(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
    (function (process,global){
        /*!
         * @overview es6-promise - a tiny implementation of Promises/A+.
         * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
         * @license   Licensed under MIT license
         *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
         * @version   3.1.2
         */

        (function() {
            "use strict";
            function lib$es6$promise$utils$$objectOrFunction(x) {
                return typeof x === 'function' || (typeof x === 'object' && x !== null);
            }

            function lib$es6$promise$utils$$isFunction(x) {
                return typeof x === 'function';
            }

            function lib$es6$promise$utils$$isMaybeThenable(x) {
                return typeof x === 'object' && x !== null;
            }

            var lib$es6$promise$utils$$_isArray;
            if (!Array.isArray) {
                lib$es6$promise$utils$$_isArray = function (x) {
                    return Object.prototype.toString.call(x) === '[object Array]';
                };
            } else {
                lib$es6$promise$utils$$_isArray = Array.isArray;
            }

            var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray;
            var lib$es6$promise$asap$$len = 0;
            var lib$es6$promise$asap$$vertxNext;
            var lib$es6$promise$asap$$customSchedulerFn;

            var lib$es6$promise$asap$$asap = function asap(callback, arg) {
                lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback;
                lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg;
                lib$es6$promise$asap$$len += 2;
                if (lib$es6$promise$asap$$len === 2) {
                    // If len is 2, that means that we need to schedule an async flush.
                    // If additional callbacks are queued before the queue is flushed, they
                    // will be processed by this flush that we are scheduling.
                    if (lib$es6$promise$asap$$customSchedulerFn) {
                        lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush);
                    } else {
                        lib$es6$promise$asap$$scheduleFlush();
                    }
                }
            }

            function lib$es6$promise$asap$$setScheduler(scheduleFn) {
                lib$es6$promise$asap$$customSchedulerFn = scheduleFn;
            }

            function lib$es6$promise$asap$$setAsap(asapFn) {
                lib$es6$promise$asap$$asap = asapFn;
            }

            var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined;
            var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {};
            var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;
            var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';

            // test for web worker but not in IE10
            var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
                typeof importScripts !== 'undefined' &&
                typeof MessageChannel !== 'undefined';

            // node
            function lib$es6$promise$asap$$useNextTick() {
                // node version 0.10.x displays a deprecation warning when nextTick is used recursively
                // see https://github.com/cujojs/when/issues/410 for details
                return function() {
                    process.nextTick(lib$es6$promise$asap$$flush);
                };
            }

            // vertx
            function lib$es6$promise$asap$$useVertxTimer() {
                return function() {
                    lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush);
                };
            }

            function lib$es6$promise$asap$$useMutationObserver() {
                var iterations = 0;
                var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);
                var node = document.createTextNode('');
                observer.observe(node, { characterData: true });

                return function() {
                    node.data = (iterations = ++iterations % 2);
                };
            }

            // web worker
            function lib$es6$promise$asap$$useMessageChannel() {
                var channel = new MessageChannel();
                channel.port1.onmessage = lib$es6$promise$asap$$flush;
                return function () {
                    channel.port2.postMessage(0);
                };
            }

            function lib$es6$promise$asap$$useSetTimeout() {
                return function() {
                    setTimeout(lib$es6$promise$asap$$flush, 1);
                };
            }

            var lib$es6$promise$asap$$queue = new Array(1000);
            function lib$es6$promise$asap$$flush() {
                for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) {
                    var callback = lib$es6$promise$asap$$queue[i];
                    var arg = lib$es6$promise$asap$$queue[i+1];

                    callback(arg);

                    lib$es6$promise$asap$$queue[i] = undefined;
                    lib$es6$promise$asap$$queue[i+1] = undefined;
                }

                lib$es6$promise$asap$$len = 0;
            }

            function lib$es6$promise$asap$$attemptVertx() {
                try {
                    var r = _dereq_;
                    var vertx = r('vertx');
                    lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext;
                    return lib$es6$promise$asap$$useVertxTimer();
                } catch(e) {
                    return lib$es6$promise$asap$$useSetTimeout();
                }
            }

            var lib$es6$promise$asap$$scheduleFlush;
            // Decide what async method to use to triggering processing of queued callbacks:
            if (lib$es6$promise$asap$$isNode) {
                lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick();
            } else if (lib$es6$promise$asap$$BrowserMutationObserver) {
                lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver();
            } else if (lib$es6$promise$asap$$isWorker) {
                lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel();
            } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof _dereq_ === 'function') {
                lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx();
            } else {
                lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout();
            }
            function lib$es6$promise$then$$then(onFulfillment, onRejection) {
                var parent = this;
                var state = parent._state;

                if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) {
                    return this;
                }

                var child = new this.constructor(lib$es6$promise$$internal$$noop);
                var result = parent._result;

                if (state) {
                    var callback = arguments[state - 1];
                    lib$es6$promise$asap$$asap(function(){
                        lib$es6$promise$$internal$$invokeCallback(state, child, callback, result);
                    });
                } else {
                    lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection);
                }

                return child;
            }
            var lib$es6$promise$then$$default = lib$es6$promise$then$$then;
            function lib$es6$promise$promise$resolve$$resolve(object) {
                /*jshint validthis:true */
                var Constructor = this;

                if (object && typeof object === 'object' && object.constructor === Constructor) {
                    return object;
                }

                var promise = new Constructor(lib$es6$promise$$internal$$noop);
                lib$es6$promise$$internal$$resolve(promise, object);
                return promise;
            }
            var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve;

            function lib$es6$promise$$internal$$noop() {}

            var lib$es6$promise$$internal$$PENDING   = void 0;
            var lib$es6$promise$$internal$$FULFILLED = 1;
            var lib$es6$promise$$internal$$REJECTED  = 2;

            var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject();

            function lib$es6$promise$$internal$$selfFulfillment() {
                return new TypeError("You cannot resolve a promise with itself");
            }

            function lib$es6$promise$$internal$$cannotReturnOwn() {
                return new TypeError('A promises callback cannot return that same promise.');
            }

            function lib$es6$promise$$internal$$getThen(promise) {
                try {
                    return promise.then;
                } catch(error) {
                    lib$es6$promise$$internal$$GET_THEN_ERROR.error = error;
                    return lib$es6$promise$$internal$$GET_THEN_ERROR;
                }
            }

            function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
                try {
                    then.call(value, fulfillmentHandler, rejectionHandler);
                } catch(e) {
                    return e;
                }
            }

            function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) {
                lib$es6$promise$asap$$asap(function(promise) {
                    var sealed = false;
                    var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) {
                        if (sealed) { return; }
                        sealed = true;
                        if (thenable !== value) {
                            lib$es6$promise$$internal$$resolve(promise, value);
                        } else {
                            lib$es6$promise$$internal$$fulfill(promise, value);
                        }
                    }, function(reason) {
                        if (sealed) { return; }
                        sealed = true;

                        lib$es6$promise$$internal$$reject(promise, reason);
                    }, 'Settle: ' + (promise._label || ' unknown promise'));

                    if (!sealed && error) {
                        sealed = true;
                        lib$es6$promise$$internal$$reject(promise, error);
                    }
                }, promise);
            }

            function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) {
                if (thenable._state === lib$es6$promise$$internal$$FULFILLED) {
                    lib$es6$promise$$internal$$fulfill(promise, thenable._result);
                } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) {
                    lib$es6$promise$$internal$$reject(promise, thenable._result);
                } else {
                    lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) {
                        lib$es6$promise$$internal$$resolve(promise, value);
                    }, function(reason) {
                        lib$es6$promise$$internal$$reject(promise, reason);
                    });
                }
            }

            function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable, then) {
                if (maybeThenable.constructor === promise.constructor &&
                    then === lib$es6$promise$then$$default &&
                    constructor.resolve === lib$es6$promise$promise$resolve$$default) {
                    lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable);
                } else {
                    if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) {
                        lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error);
                    } else if (then === undefined) {
                        lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
                    } else if (lib$es6$promise$utils$$isFunction(then)) {
                        lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then);
                    } else {
                        lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
                    }
                }
            }

            function lib$es6$promise$$internal$$resolve(promise, value) {
                if (promise === value) {
                    lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment());
                } else if (lib$es6$promise$utils$$objectOrFunction(value)) {
                    lib$es6$promise$$internal$$handleMaybeThenable(promise, value, lib$es6$promise$$internal$$getThen(value));
                } else {
                    lib$es6$promise$$internal$$fulfill(promise, value);
                }
            }

            function lib$es6$promise$$internal$$publishRejection(promise) {
                if (promise._onerror) {
                    promise._onerror(promise._result);
                }

                lib$es6$promise$$internal$$publish(promise);
            }

            function lib$es6$promise$$internal$$fulfill(promise, value) {
                if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }

                promise._result = value;
                promise._state = lib$es6$promise$$internal$$FULFILLED;

                if (promise._subscribers.length !== 0) {
                    lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise);
                }
            }

            function lib$es6$promise$$internal$$reject(promise, reason) {
                if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
                promise._state = lib$es6$promise$$internal$$REJECTED;
                promise._result = reason;

                lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise);
            }

            function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
                var subscribers = parent._subscribers;
                var length = subscribers.length;

                parent._onerror = null;

                subscribers[length] = child;
                subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment;
                subscribers[length + lib$es6$promise$$internal$$REJECTED]  = onRejection;

                if (length === 0 && parent._state) {
                    lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent);
                }
            }

            function lib$es6$promise$$internal$$publish(promise) {
                var subscribers = promise._subscribers;
                var settled = promise._state;

                if (subscribers.length === 0) { return; }

                var child, callback, detail = promise._result;

                for (var i = 0; i < subscribers.length; i += 3) {
                    child = subscribers[i];
                    callback = subscribers[i + settled];

                    if (child) {
                        lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail);
                    } else {
                        callback(detail);
                    }
                }

                promise._subscribers.length = 0;
            }

            function lib$es6$promise$$internal$$ErrorObject() {
                this.error = null;
            }

            var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject();

            function lib$es6$promise$$internal$$tryCatch(callback, detail) {
                try {
                    return callback(detail);
                } catch(e) {
                    lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e;
                    return lib$es6$promise$$internal$$TRY_CATCH_ERROR;
                }
            }

            function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) {
                var hasCallback = lib$es6$promise$utils$$isFunction(callback),
                    value, error, succeeded, failed;

                if (hasCallback) {
                    value = lib$es6$promise$$internal$$tryCatch(callback, detail);

                    if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) {
                        failed = true;
                        error = value.error;
                        value = null;
                    } else {
                        succeeded = true;
                    }

                    if (promise === value) {
                        lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn());
                        return;
                    }

                } else {
                    value = detail;
                    succeeded = true;
                }

                if (promise._state !== lib$es6$promise$$internal$$PENDING) {
                    // noop
                } else if (hasCallback && succeeded) {
                    lib$es6$promise$$internal$$resolve(promise, value);
                } else if (failed) {
                    lib$es6$promise$$internal$$reject(promise, error);
                } else if (settled === lib$es6$promise$$internal$$FULFILLED) {
                    lib$es6$promise$$internal$$fulfill(promise, value);
                } else if (settled === lib$es6$promise$$internal$$REJECTED) {
                    lib$es6$promise$$internal$$reject(promise, value);
                }
            }

            function lib$es6$promise$$internal$$initializePromise(promise, resolver) {
                try {
                    resolver(function resolvePromise(value){
                        lib$es6$promise$$internal$$resolve(promise, value);
                    }, function rejectPromise(reason) {
                        lib$es6$promise$$internal$$reject(promise, reason);
                    });
                } catch(e) {
                    lib$es6$promise$$internal$$reject(promise, e);
                }
            }

            function lib$es6$promise$promise$all$$all(entries) {
                return new lib$es6$promise$enumerator$$default(this, entries).promise;
            }
            var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all;
            function lib$es6$promise$promise$race$$race(entries) {
                /*jshint validthis:true */
                var Constructor = this;

                var promise = new Constructor(lib$es6$promise$$internal$$noop);

                if (!lib$es6$promise$utils$$isArray(entries)) {
                    lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
                    return promise;
                }

                var length = entries.length;

                function onFulfillment(value) {
                    lib$es6$promise$$internal$$resolve(promise, value);
                }

                function onRejection(reason) {
                    lib$es6$promise$$internal$$reject(promise, reason);
                }

                for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
                    lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
                }

                return promise;
            }
            var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race;
            function lib$es6$promise$promise$reject$$reject(reason) {
                /*jshint validthis:true */
                var Constructor = this;
                var promise = new Constructor(lib$es6$promise$$internal$$noop);
                lib$es6$promise$$internal$$reject(promise, reason);
                return promise;
            }
            var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject;

            var lib$es6$promise$promise$$counter = 0;

            function lib$es6$promise$promise$$needsResolver() {
                throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
            }

            function lib$es6$promise$promise$$needsNew() {
                throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
            }

            var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise;
            /**
             Promise objects represent the eventual result of an asynchronous operation. The
             primary way of interacting with a promise is through its `then` method, which
             registers callbacks to receive either a promise's eventual value or the reason
             why the promise cannot be fulfilled.

             Terminology
             -----------

             - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
             - `thenable` is an object or function that defines a `then` method.
             - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
             - `exception` is a value that is thrown using the throw statement.
             - `reason` is a value that indicates why a promise was rejected.
             - `settled` the final resting state of a promise, fulfilled or rejected.

             A promise can be in one of three states: pending, fulfilled, or rejected.

             Promises that are fulfilled have a fulfillment value and are in the fulfilled
             state.  Promises that are rejected have a rejection reason and are in the
             rejected state.  A fulfillment value is never a thenable.

             Promises can also be said to *resolve* a value.  If this value is also a
             promise, then the original promise's settled state will match the value's
             settled state.  So a promise that *resolves* a promise that rejects will
             itself reject, and a promise that *resolves* a promise that fulfills will
             itself fulfill.


             Basic Usage:
             ------------

             ```js
             var promise = new Promise(function(resolve, reject) {
        // on success
        resolve(value);

        // on failure
        reject(reason);
      });

             promise.then(function(value) {
        // on fulfillment
      }, function(reason) {
        // on rejection
      });
             ```

             Advanced Usage:
             ---------------

             Promises shine when abstracting away asynchronous interactions such as
             `XMLHttpRequest`s.

             ```js
             function getJSON(url) {
        return new Promise(function(resolve, reject){
          var xhr = new XMLHttpRequest();

          xhr.open('GET', url);
          xhr.onreadystatechange = handler;
          xhr.responseType = 'json';
          xhr.setRequestHeader('Accept', 'application/json');
          xhr.send();

          function handler() {
            if (this.readyState === this.DONE) {
              if (this.status === 200) {
                resolve(this.response);
              } else {
                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
              }
            }
          };
        });
      }

             getJSON('/posts.json').then(function(json) {
        // on fulfillment
      }, function(reason) {
        // on rejection
      });
             ```

             Unlike callbacks, promises are great composable primitives.

             ```js
             Promise.all([
             getJSON('/posts'),
             getJSON('/comments')
             ]).then(function(values){
        values[0] // => postsJSON
        values[1] // => commentsJSON

        return values;
      });
             ```

             @class Promise
             @param {function} resolver
             Useful for tooling.
             @constructor
             */
            function lib$es6$promise$promise$$Promise(resolver) {
                this._id = lib$es6$promise$promise$$counter++;
                this._state = undefined;
                this._result = undefined;
                this._subscribers = [];

                if (lib$es6$promise$$internal$$noop !== resolver) {
                    typeof resolver !== 'function' && lib$es6$promise$promise$$needsResolver();
                    this instanceof lib$es6$promise$promise$$Promise ? lib$es6$promise$$internal$$initializePromise(this, resolver) : lib$es6$promise$promise$$needsNew();
                }
            }

            lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default;
            lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default;
            lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default;
            lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default;
            lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler;
            lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap;
            lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap;

            lib$es6$promise$promise$$Promise.prototype = {
                constructor: lib$es6$promise$promise$$Promise,

                /**
                 The primary way of interacting with a promise is through its `then` method,
                 which registers callbacks to receive either a promise's eventual value or the
                 reason why the promise cannot be fulfilled.

                 ```js
                 findUser().then(function(user){
        // user is available
      }, function(reason){
        // user is unavailable, and you are given the reason why
      });
                 ```

                 Chaining
                 --------

                 The return value of `then` is itself a promise.  This second, 'downstream'
                 promise is resolved with the return value of the first promise's fulfillment
                 or rejection handler, or rejected if the handler throws an exception.

                 ```js
                 findUser().then(function (user) {
        return user.name;
      }, function (reason) {
        return 'default name';
      }).then(function (userName) {
        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
        // will be `'default name'`
      });

                 findUser().then(function (user) {
        throw new Error('Found user, but still unhappy');
      }, function (reason) {
        throw new Error('`findUser` rejected and we're unhappy');
      }).then(function (value) {
        // never reached
      }, function (reason) {
        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
      });
                 ```
                 If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.

                 ```js
                 findUser().then(function (user) {
        throw new PedagogicalException('Upstream error');
      }).then(function (value) {
        // never reached
      }).then(function (value) {
        // never reached
      }, function (reason) {
        // The `PedgagocialException` is propagated all the way down to here
      });
                 ```

                 Assimilation
                 ------------

                 Sometimes the value you want to propagate to a downstream promise can only be
                 retrieved asynchronously. This can be achieved by returning a promise in the
                 fulfillment or rejection handler. The downstream promise will then be pending
                 until the returned promise is settled. This is called *assimilation*.

                 ```js
                 findUser().then(function (user) {
        return findCommentsByAuthor(user);
      }).then(function (comments) {
        // The user's comments are now available
      });
                 ```

                 If the assimliated promise rejects, then the downstream promise will also reject.

                 ```js
                 findUser().then(function (user) {
        return findCommentsByAuthor(user);
      }).then(function (comments) {
        // If `findCommentsByAuthor` fulfills, we'll have the value here
      }, function (reason) {
        // If `findCommentsByAuthor` rejects, we'll have the reason here
      });
                 ```

                 Simple Example
                 --------------

                 Synchronous Example

                 ```javascript
                 var result;

                 try {
        result = findResult();
        // success
      } catch(reason) {
        // failure
      }
                 ```

                 Errback Example

                 ```js
                 findResult(function(result, err){
        if (err) {
          // failure
        } else {
          // success
        }
      });
                 ```

                 Promise Example;

                 ```javascript
                 findResult().then(function(result){
        // success
      }, function(reason){
        // failure
      });
                 ```

                 Advanced Example
                 --------------

                 Synchronous Example

                 ```javascript
                 var author, books;

                 try {
        author = findAuthor();
        books  = findBooksByAuthor(author);
        // success
      } catch(reason) {
        // failure
      }
                 ```

                 Errback Example

                 ```js

                 function foundBooks(books) {

      }

                 function failure(reason) {

      }

                 findAuthor(function(author, err){
        if (err) {
          failure(err);
          // failure
        } else {
          try {
            findBoooksByAuthor(author, function(books, err) {
              if (err) {
                failure(err);
              } else {
                try {
                  foundBooks(books);
                } catch(reason) {
                  failure(reason);
                }
              }
            });
          } catch(error) {
            failure(err);
          }
          // success
        }
      });
                 ```

                 Promise Example;

                 ```javascript
                 findAuthor().
                 then(findBooksByAuthor).
                 then(function(books){
          // found books
      }).catch(function(reason){
        // something went wrong
      });
                 ```

                 @method then
                 @param {Function} onFulfilled
                 @param {Function} onRejected
                 Useful for tooling.
                 @return {Promise}
                 */
                then: lib$es6$promise$then$$default,

                /**
                 `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
                 as the catch block of a try/catch statement.

                 ```js
                 function findAuthor(){
        throw new Error('couldn't find that author');
      }

                 // synchronous
                 try {
        findAuthor();
      } catch(reason) {
        // something went wrong
      }

                 // async with promises
                 findAuthor().catch(function(reason){
        // something went wrong
      });
                 ```

                 @method catch
                 @param {Function} onRejection
                 Useful for tooling.
                 @return {Promise}
                 */
                'catch': function(onRejection) {
                    return this.then(null, onRejection);
                }
            };
            var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator;
            function lib$es6$promise$enumerator$$Enumerator(Constructor, input) {
                this._instanceConstructor = Constructor;
                this.promise = new Constructor(lib$es6$promise$$internal$$noop);

                if (Array.isArray(input)) {
                    this._input     = input;
                    this.length     = input.length;
                    this._remaining = input.length;

                    this._result = new Array(this.length);

                    if (this.length === 0) {
                        lib$es6$promise$$internal$$fulfill(this.promise, this._result);
                    } else {
                        this.length = this.length || 0;
                        this._enumerate();
                        if (this._remaining === 0) {
                            lib$es6$promise$$internal$$fulfill(this.promise, this._result);
                        }
                    }
                } else {
                    lib$es6$promise$$internal$$reject(this.promise, this._validationError());
                }
            }

            lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() {
                return new Error('Array Methods must be provided an Array');
            };

            lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() {
                var length  = this.length;
                var input   = this._input;

                for (var i = 0; this._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
                    this._eachEntry(input[i], i);
                }
            };

            lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
                var c = this._instanceConstructor;
                var resolve = c.resolve;

                if (resolve === lib$es6$promise$promise$resolve$$default) {
                    var then = lib$es6$promise$$internal$$getThen(entry);

                    if (then === lib$es6$promise$then$$default &&
                        entry._state !== lib$es6$promise$$internal$$PENDING) {
                        this._settledAt(entry._state, i, entry._result);
                    } else if (typeof then !== 'function') {
                        this._remaining--;
                        this._result[i] = entry;
                    } else if (c === lib$es6$promise$promise$$default) {
                        var promise = new c(lib$es6$promise$$internal$$noop);
                        lib$es6$promise$$internal$$handleMaybeThenable(promise, entry, then);
                        this._willSettleAt(promise, i);
                    } else {
                        this._willSettleAt(new c(function(resolve) { resolve(entry); }), i);
                    }
                } else {
                    this._willSettleAt(resolve(entry), i);
                }
            };

            lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
                var promise = this.promise;

                if (promise._state === lib$es6$promise$$internal$$PENDING) {
                    this._remaining--;

                    if (state === lib$es6$promise$$internal$$REJECTED) {
                        lib$es6$promise$$internal$$reject(promise, value);
                    } else {
                        this._result[i] = value;
                    }
                }

                if (this._remaining === 0) {
                    lib$es6$promise$$internal$$fulfill(promise, this._result);
                }
            };

            lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
                var enumerator = this;

                lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) {
                    enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value);
                }, function(reason) {
                    enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason);
                });
            };
            function lib$es6$promise$polyfill$$polyfill() {
                var local;

                if (typeof global !== 'undefined') {
                    local = global;
                } else if (typeof self !== 'undefined') {
                    local = self;
                } else {
                    try {
                        local = Function('return this')();
                    } catch (e) {
                        throw new Error('polyfill failed because global object is unavailable in this environment');
                    }
                }

                var P = local.Promise;

                if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) {
                    return;
                }

                local.Promise = lib$es6$promise$promise$$default;
            }
            var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill;

            var lib$es6$promise$umd$$ES6Promise = {
                'Promise': lib$es6$promise$promise$$default,
                'polyfill': lib$es6$promise$polyfill$$default
            };

            /* global define:true module:true window: true */
            if (typeof define === 'function' && define['amd']) {
                define(function() { return lib$es6$promise$umd$$ES6Promise; });
            } else if (typeof module !== 'undefined' && module['exports']) {
                module['exports'] = lib$es6$promise$umd$$ES6Promise;
            } else if (typeof this !== 'undefined') {
                this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise;
            }

            lib$es6$promise$polyfill$$default();
        }).call(this);


    }).call(this,_dereq_('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"_process":26}],2:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var Util = _dereq_('./util.js');
    var WakeLock = _dereq_('./wakelock.js');

// Start at a higher number to reduce chance of conflict.
    var nextDisplayId = 1000;

    /**
     * The base class for all VR displays.
     */
    function VRDisplay() {
        this.displayId = nextDisplayId++;
        this.displayName = 'webvr-polyfill displayName';

        this.isConnected = true;
        this.isPresenting = false;
        this.capabilities = {
            hasPosition: false,
            hasOrientation: false,
            hasExternalDisplay: false,
            canPresent: false
        };
        this.stageParameters = null;

        // "Private" members.
        this.waitingForPresent_ = false;
        this.layer_ = null;

        this.fullscreenElement_ = null;
        this.fullscreenWrapper_ = null;

        this.fullscreenEventTarget_ = null;
        this.fullscreenChangeHandler_ = null;
        this.fullscreenErrorHandler_ = null;

        this.wakelock_ = new WakeLock();
    }

    VRDisplay.prototype.getPose = function() {
        // TODO: Technically this should retain it's value for the duration of a frame
        // but I doubt that's practical to do in javascript.
        return this.getImmediatePose();
    };

    VRDisplay.prototype.requestAnimationFrame = function(callback) {
        return window.requestAnimationFrame(callback);
    };

    VRDisplay.prototype.cancelAnimationFrame = function(id) {
        return window.cancelAnimationFrame(id);
    };

    VRDisplay.prototype.wrapForFullscreen = function(element) {
        if (Util.isIOS())
            return element;

        if (!this.fullscreenWrapper_) {
            this.fullscreenWrapper_ = document.createElement('div');
            this.fullscreenWrapper_.classList.add('webvr-polyfill-fullscreen-wrapper');
            // Make sure the wrapper takes the full screen. Without this, there is a
            // white line at the bottom.
            this.fullscreenWrapper_.style.width = '100%';
            this.fullscreenWrapper_.style.height = '100%';
        }

        if (this.fullscreenElement_ == element)
            return this.fullscreenWrapper_;

        // Remove any previously applied wrappers
        this.removeFullscreenWrapper();

        this.fullscreenElement_ = element;
        var parent = this.fullscreenElement_.parentElement;
        parent.insertBefore(this.fullscreenWrapper_, this.fullscreenElement_);
        parent.removeChild(this.fullscreenElement_);
        this.fullscreenWrapper_.insertBefore(this.fullscreenElement_, this.fullscreenWrapper_.firstChild);

        return this.fullscreenWrapper_;
    };

    VRDisplay.prototype.removeFullscreenWrapper = function() {
        if (!this.fullscreenElement_)
            return;

        var element = this.fullscreenElement_;
        this.fullscreenElement_ = null;

        var parent = this.fullscreenWrapper_.parentElement;
        this.fullscreenWrapper_.removeChild(element);
        parent.insertBefore(element, this.fullscreenWrapper_);
        parent.removeChild(this.fullscreenWrapper_);

        return element;
    };

    VRDisplay.prototype.requestPresent = function(layer) {
        // Always use document.body for the fullscreen element, since we want to be
        // able to render UI on top of the WebGL canvas.
        //var fullscreenElement = layer.source;

        var self = this;
        this.layer_ = layer;

        return new Promise(function(resolve, reject) {
            if (!self.capabilities.canPresent) {
                reject(new Error('VRDisplay is not capable of presenting.'));
                return;
            }

            self.waitingForPresent_ = false;
            if (layer && layer.source) {
                var fullscreenElement = self.wrapForFullscreen(layer.source);

                function onFullscreenChange() {
                    var actualFullscreenElement = Util.getFullscreenElement();

                    self.isPresenting = (fullscreenElement === actualFullscreenElement);
                    self.fireVRDisplayPresentChange_();
                    if (self.isPresenting) {
                        if (screen.orientation && screen.orientation.lock)
                            screen.orientation.lock('landscape-primary');
                        self.waitingForPresent_ = false;
                        self.beginPresent_();
                        resolve();
                    } else {
                        if (screen.orientation && screen.orientation.unlock)
                            screen.orientation.unlock();
                        self.removeFullscreenWrapper();
                        self.wakelock_.release();
                        self.endPresent_();
                        self.removeFullscreenListeners_();
                    }
                }
                function onFullscreenError() {
                    if (!self.waitingForPresent_)
                        return;

                    self.removeFullscreenWrapper();
                    self.removeFullscreenListeners_();

                    self.wakelock_.release();
                    self.waitingForPresent_ = false;
                    self.isPresenting = false;

                    reject(new Error('Unable to present.'));
                }

                self.addFullscreenListeners_(fullscreenElement,
                    onFullscreenChange, onFullscreenError);

                if (Util.requestFullscreen(fullscreenElement)) {
                    self.wakelock_.request();
                    self.waitingForPresent_ = true;
                } else if (Util.isIOS()) {
                    // *sigh* Just fake it.
                    self.wakelock_.request();
                    self.isPresenting = true;
                    self.fireVRDisplayPresentChange_();
                    self.beginPresent_();
                    resolve();
                }
            }

            if (!self.waitingForPresent_ && !Util.isIOS()) {
                Util.exitFullscreen();
                reject(new Error('Unable to present.'));
            }
        });
    };

    VRDisplay.prototype.exitPresent = function() {
        var wasPresenting = this.isPresenting;
        var self = this;
        this.isPresenting = false;
        this.layer_ = null;
        this.wakelock_.release();

        return new Promise(function(resolve, reject) {
            if (wasPresenting) {
                if (!Util.exitFullscreen() && Util.isIOS()) {
                    self.endPresent_();
                }

                self.removeFullscreenWrapper();

                resolve();
            } else {
                reject(new Error('Was not presenting to VRDisplay.'));
            }
        });
    };

// This returns an array because future versions of the spec may accept multiple
// layers in requestPresent, and it's easier to overload function parameters
// than it is return types.
    VRDisplay.prototype.getLayers = function() {
        if (this.layer_) {
            return [this.layer_];
        }
        return null;
    };

    VRDisplay.prototype.fireVRDisplayPresentChange_ = function() {
        var event = new CustomEvent('vrdisplaypresentchange', {detail: {vrdisplay: this}});
        window.dispatchEvent(event);
    };

    VRDisplay.prototype.addFullscreenListeners_ = function(element, changeHandler, errorHandler) {
        this.removeFullscreenListeners_();

        this.fullscreenEventTarget_ = element;
        this.fullscreenChangeHandler_ = changeHandler;
        this.fullscreenErrorHandler_ = errorHandler;

        if (changeHandler) {
            element.addEventListener('fullscreenchange', changeHandler, false);
            element.addEventListener('webkitfullscreenchange', changeHandler, false);
            document.addEventListener('mozfullscreenchange', changeHandler, false);
            element.addEventListener('msfullscreenchange', changeHandler, false);
        }

        if (errorHandler) {
            element.addEventListener('fullscreenerror', errorHandler, false);
            element.addEventListener('webkitfullscreenerror', errorHandler, false);
            document.addEventListener('mozfullscreenerror', errorHandler, false);
            element.addEventListener('msfullscreenerror', errorHandler, false);
        }
    };

    VRDisplay.prototype.removeFullscreenListeners_ = function() {
        if (!this.fullscreenEventTarget_)
            return;

        var element = this.fullscreenEventTarget_;

        if (this.fullscreenChangeHandler_) {
            var changeHandler = this.fullscreenChangeHandler_;
            element.removeEventListener('fullscreenchange', changeHandler, false);
            element.removeEventListener('webkitfullscreenchange', changeHandler, false);
            document.removeEventListener('mozfullscreenchange', changeHandler, false);
            element.removeEventListener('msfullscreenchange', changeHandler, false);
        }

        if (this.fullscreenErrorHandler_) {
            var errorHandler = this.fullscreenErrorHandler_;
            element.removeEventListener('fullscreenerror', errorHandler, false);
            element.removeEventListener('webkitfullscreenerror', errorHandler, false);
            document.removeEventListener('mozfullscreenerror', errorHandler, false);
            element.removeEventListener('msfullscreenerror', errorHandler, false);
        }

        this.fullscreenEventTarget_ = null;
        this.fullscreenChangeHandler_ = null;
        this.fullscreenErrorHandler_ = null;
    };

    VRDisplay.prototype.beginPresent_ = function() {
        // Override to add custom behavior when presentation begins.
    };

    VRDisplay.prototype.endPresent_ = function() {
        // Override to add custom behavior when presentation ends.
    };

    VRDisplay.prototype.submitFrame = function(pose) {
        // Override to add custom behavior for frame submission.
    };

    VRDisplay.prototype.getEyeParameters = function(whichEye) {
        // Override to return accurate eye parameters is canPresent is true.
        return null;
    };

    /*
     * Deprecated classes
     */

    /**
     * The base class for all VR devices. (Deprecated)
     */
    function VRDevice() {
        this.hardwareUnitId = 'webvr-polyfill hardwareUnitId';
        this.deviceId = 'webvr-polyfill deviceId';
        this.deviceName = 'webvr-polyfill deviceName';
    }

    /**
     * The base class for all VR HMD devices. (Deprecated)
     */
    function HMDVRDevice() {
    }
    HMDVRDevice.prototype = new VRDevice();

    /**
     * The base class for all VR position sensor devices. (Deprecated)
     */
    function PositionSensorVRDevice() {
    }
    PositionSensorVRDevice.prototype = new VRDevice();

    module.exports.VRDisplay = VRDisplay;
    module.exports.VRDevice = VRDevice;
    module.exports.HMDVRDevice = HMDVRDevice;
    module.exports.PositionSensorVRDevice = PositionSensorVRDevice;

},{"./util.js":22,"./wakelock.js":24}],3:[function(_dereq_,module,exports){
    /*
     * Copyright 2016 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var CardboardUI = _dereq_('./cardboard-ui.js');
    var Util = _dereq_('./util.js');
    var WGLUPreserveGLState = _dereq_('./deps/wglu-preserve-state.js');

    var distortionVS = [
        'attribute vec2 position;',
        'attribute vec3 texCoord;',

        'varying vec2 vTexCoord;',

        'uniform vec4 viewportOffsetScale[2];',

        'void main() {',
        '  vec4 viewport = viewportOffsetScale[int(texCoord.z)];',
        '  vTexCoord = (texCoord.xy * viewport.zw) + viewport.xy;',
        '  gl_Position = vec4( position, 1.0, 1.0 );',
        '}',
    ].join('\n');

    var distortionFS = [
        'precision mediump float;',
        'uniform sampler2D diffuse;',

        'varying vec2 vTexCoord;',

        'void main() {',
        '  gl_FragColor = texture2D(diffuse, vTexCoord);',
        '}',
    ].join('\n');

    /**
     * A mesh-based distorter.
     */
    function CardboardDistorter(gl) {
        this.gl = gl;
        this.ctxAttribs = gl.getContextAttributes();

        this.meshWidth = 20;
        this.meshHeight = 20;

        this.bufferScale = WebVRConfig.BUFFER_SCALE ? WebVRConfig.BUFFER_SCALE : 1.0;

        this.bufferWidth = gl.drawingBufferWidth;
        this.bufferHeight = gl.drawingBufferHeight;

        // Patching support
        this.realBindFramebuffer = gl.bindFramebuffer;
        this.realEnable = gl.enable;
        this.realDisable = gl.disable;
        this.realColorMask = gl.colorMask;
        this.realClearColor = gl.clearColor;
        this.realViewport = gl.viewport;
        this.realCanvasWidth = Object.getOwnPropertyDescriptor(gl.canvas.__proto__, 'width');
        this.realCanvasHeight = Object.getOwnPropertyDescriptor(gl.canvas.__proto__, 'height');

        this.isPatched = false;

        // State tracking
        this.lastBoundFramebuffer = null;
        this.cullFace = false;
        this.depthTest = false;
        this.blend = false;
        this.scissorTest = false;
        this.stencilTest = false;
        this.viewport = [0, 0, 0, 0];
        this.colorMask = [true, true, true, true];
        this.clearColor = [0, 0, 0, 0];

        this.attribs = {
            position: 0,
            texCoord: 1
        };
        this.program = Util.linkProgram(gl, distortionVS, distortionFS, this.attribs);
        this.uniforms = Util.getProgramUniforms(gl, this.program);

        this.viewportOffsetScale = new Float32Array(8);
        this.setTextureBounds();

        this.vertexBuffer = gl.createBuffer();
        this.indexBuffer = gl.createBuffer();
        this.indexCount = 0;

        this.renderTarget = gl.createTexture();
        this.framebuffer = gl.createFramebuffer();

        this.depthStencilBuffer = null;
        this.depthBuffer = null;
        this.stencilBuffer = null;

        if (this.ctxAttribs.depth && this.ctxAttribs.stencil) {
            this.depthStencilBuffer = gl.createRenderbuffer();
        } else if (this.ctxAttribs.depth) {
            this.depthBuffer = gl.createRenderbuffer();
        } else if (this.ctxAttribs.stencil) {
            this.stencilBuffer = gl.createRenderbuffer();
        }

        this.patch();

        this.onResize();

        this.cardboardUI = new CardboardUI(gl);
    };

    /**
     * Tears down all the resources created by the distorter and removes any
     * patches.
     */
    CardboardDistorter.prototype.destroy = function() {
        var gl = this.gl;

        this.unpatch();

        gl.deleteProgram(this.program);
        gl.deleteBuffer(this.vertexBuffer);
        gl.deleteBuffer(this.indexBuffer);
        gl.deleteTexture(this.renderTarget);
        gl.deleteFramebuffer(this.framebuffer);
        if (this.depthStencilBuffer) {
            gl.deleteRenderbuffer(this.depthStencilBuffer);
        }
        if (this.depthBuffer) {
            gl.deleteRenderbuffer(this.depthBuffer);
        }
        if (this.stencilBuffer) {
            gl.deleteRenderbuffer(this.stencilBuffer);
        }

        this.cardboardUI.destroy();
    };


    /**
     * Resizes the backbuffer to match the canvas width and height.
     */
    CardboardDistorter.prototype.onResize = function() {
        var gl = this.gl;
        var self = this;

        var glState = [
            gl.RENDERBUFFER_BINDING,
            gl.TEXTURE_BINDING_2D, gl.TEXTURE0
        ];

        WGLUPreserveGLState(gl, glState, function(gl) {
            // Bind real backbuffer and clear it once. We don't need to clear it again
            // after that because we're overwriting the same area every frame.
            self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, null);

            // Put things in a good state
            if (self.scissorTest) { self.realDisable.call(gl, gl.SCISSOR_TEST); }
            self.realColorMask.call(gl, true, true, true, true);
            self.realViewport.call(gl, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
            self.realClearColor.call(gl, 0, 0, 0, 1);

            gl.clear(gl.COLOR_BUFFER_BIT);

            // Now bind and resize the fake backbuffer
            self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.framebuffer);

            gl.bindTexture(gl.TEXTURE_2D, self.renderTarget);
            gl.texImage2D(gl.TEXTURE_2D, 0, self.ctxAttribs.alpha ? gl.RGBA : gl.RGB,
                self.bufferWidth, self.bufferHeight, 0,
                self.ctxAttribs.alpha ? gl.RGBA : gl.RGB, gl.UNSIGNED_BYTE, null);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, self.renderTarget, 0);

            if (self.ctxAttribs.depth && self.ctxAttribs.stencil) {
                gl.bindRenderbuffer(gl.RENDERBUFFER, self.depthStencilBuffer);
                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL,
                    self.bufferWidth, self.bufferHeight);
                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT,
                    gl.RENDERBUFFER, self.depthStencilBuffer);
            } else if (self.ctxAttribs.depth) {
                gl.bindRenderbuffer(gl.RENDERBUFFER, self.depthBuffer);
                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16,
                    self.bufferWidth, self.bufferHeight);
                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
                    gl.RENDERBUFFER, self.depthBuffer);
            } else if (self.ctxAttribs.stencil) {
                gl.bindRenderbuffer(gl.RENDERBUFFER, self.stencilBuffer);
                gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8,
                    self.bufferWidth, self.bufferHeight);
                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT,
                    gl.RENDERBUFFER, self.stencilBuffer);
            }

            if (!gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
                console.error('Framebuffer incomplete!');
            }

            self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.lastBoundFramebuffer);

            if (self.scissorTest) { self.realEnable.call(gl, gl.SCISSOR_TEST); }

            self.realColorMask.apply(gl, self.colorMask);
            self.realViewport.apply(gl, self.viewport);
            self.realClearColor.apply(gl, self.clearColor);
        });

        if (this.cardboardUI) {
            this.cardboardUI.onResize();
        }
    };

    CardboardDistorter.prototype.patch = function() {
        if (this.isPatched) {
            return;
        }

        var self = this;
        var canvas = this.gl.canvas;
        var gl = this.gl;

        canvas.width = Util.getScreenWidth() * this.bufferScale;
        canvas.height = Util.getScreenHeight() * this.bufferScale;

        Object.defineProperty(canvas, 'width', {
            configurable: true,
            enumerable: true,
            get: function() {
                return self.bufferWidth;
            },
            set: function(value) {
                self.bufferWidth = value;
                self.onResize();
            }
        });

        Object.defineProperty(canvas, 'height', {
            configurable: true,
            enumerable: true,
            get: function() {
                return self.bufferHeight;
            },
            set: function(value) {
                self.bufferHeight = value;
                self.onResize();
            }
        });

        this.lastBoundFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);

        if (this.lastBoundFramebuffer == null) {
            this.lastBoundFramebuffer = this.framebuffer;
            this.gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
        }

        this.gl.bindFramebuffer = function(target, framebuffer) {
            self.lastBoundFramebuffer = framebuffer ? framebuffer : self.framebuffer;
            // Silently make calls to bind the default framebuffer bind ours instead.
            self.realBindFramebuffer.call(gl, target, self.lastBoundFramebuffer);
        };

        this.cullFace = gl.getParameter(gl.CULL_FACE);
        this.depthTest = gl.getParameter(gl.DEPTH_TEST);
        this.blend = gl.getParameter(gl.BLEND);
        this.scissorTest = gl.getParameter(gl.SCISSOR_TEST);
        this.stencilTest = gl.getParameter(gl.STENCIL_TEST);

        gl.enable = function(pname) {
            switch(pname) {
                case gl.CULL_FACE: self.cullFace = true; break;
                case gl.DEPTH_TEST: self.depthTest = true; break;
                case gl.BLEND: self.blend = true; break;
                case gl.SCISSOR_TEST: self.scissorTest = true; break;
                case gl.STENCIL_TEST: self.stencilTest = true; break;
            }
            self.realEnable.call(gl, pname);
        };

        gl.disable = function(pname) {
            switch(pname) {
                case gl.CULL_FACE: self.cullFace = false; break;
                case gl.DEPTH_TEST: self.depthTest = false; break;
                case gl.BLEND: self.blend = false; break;
                case gl.SCISSOR_TEST: self.scissorTest = false; break;
                case gl.STENCIL_TEST: self.stencilTest = false; break;
            }
            self.realDisable.call(gl, pname);
        };

        this.colorMask = gl.getParameter(gl.COLOR_WRITEMASK);
        gl.colorMask = function(r, g, b, a) {
            self.colorMask[0] = r;
            self.colorMask[1] = g;
            self.colorMask[2] = b;
            self.colorMask[3] = a;
            self.realColorMask.call(gl, r, g, b, a);
        };

        this.clearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE);
        gl.clearColor = function(r, g, b, a) {
            self.clearColor[0] = r;
            self.clearColor[1] = g;
            self.clearColor[2] = b;
            self.clearColor[3] = a;
            self.realClearColor.call(gl, r, g, b, a);
        };

        this.viewport = gl.getParameter(gl.VIEWPORT);
        gl.viewport = function(x, y, w, h) {
            self.viewport[0] = x;
            self.viewport[1] = y;
            self.viewport[2] = w;
            self.viewport[3] = h;
            self.realViewport.call(gl, x, y, w, h);
        };

        this.isPatched = true;
    };

    CardboardDistorter.prototype.unpatch = function() {
        if (!this.isPatched) {
            return;
        }

        var gl = this.gl;
        var canvas = this.gl.canvas;

        Object.defineProperty(canvas, 'width', this.realCanvasWidth);
        Object.defineProperty(canvas, 'height', this.realCanvasHeight);
        canvas.width = this.bufferWidth;
        canvas.height = this.bufferHeight;

        gl.bindFramebuffer = this.realBindFramebuffer;
        gl.enable = this.realEnable;
        gl.disable = this.realDisable;
        gl.colorMask = this.realColorMask;
        gl.clearColor = this.realClearColor;
        gl.viewport = this.realViewport;

        // Check to see if our fake backbuffer is bound and bind the real backbuffer
        // if that's the case.
        if (this.lastBoundFramebuffer == this.framebuffer) {
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        }

        this.isPatched = false;
    };

    CardboardDistorter.prototype.setTextureBounds = function(leftBounds, rightBounds) {
        if (!leftBounds)
            leftBounds = [0, 0, 0.5, 1];

        if (!rightBounds)
            rightBounds = [0.5, 0, 0.5, 1];

        // Left eye
        this.viewportOffsetScale[0] = leftBounds[0]; // X
        this.viewportOffsetScale[1] = leftBounds[1]; // Y
        this.viewportOffsetScale[2] = leftBounds[2]; // Width
        this.viewportOffsetScale[3] = leftBounds[3]; // Height

        // Right eye
        this.viewportOffsetScale[4] = rightBounds[0]; // X
        this.viewportOffsetScale[5] = rightBounds[1]; // Y
        this.viewportOffsetScale[6] = rightBounds[2]; // Width
        this.viewportOffsetScale[7] = rightBounds[3]; // Height
    };

    /**
     * Performs distortion pass on the injected backbuffer, rendering it to the real
     * backbuffer.
     */
    CardboardDistorter.prototype.submitFrame = function() {
        var gl = this.gl;
        var self = this;

        var glState = [];

        if (!WebVRConfig.DIRTY_SUBMIT_FRAME_BINDINGS) {
            glState.push(
                gl.CURRENT_PROGRAM,
                gl.ARRAY_BUFFER_BINDING,
                gl.ELEMENT_ARRAY_BUFFER_BINDING,
                gl.TEXTURE_BINDING_2D, gl.TEXTURE0
            );
        }

        WGLUPreserveGLState(gl, glState, function(gl) {
            // Bind the real default framebuffer
            self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, null);

            // Make sure the GL state is in a good place
            if (self.cullFace) { self.realDisable.call(gl, gl.CULL_FACE); }
            if (self.depthTest) { self.realDisable.call(gl, gl.DEPTH_TEST); }
            if (self.blend) { self.realDisable.call(gl, gl.BLEND); }
            if (self.scissorTest) { self.realDisable.call(gl, gl.SCISSOR_TEST); }
            if (self.stencilTest) { self.realDisable.call(gl, gl.STENCIL_TEST); }
            self.realColorMask.call(gl, true, true, true, true);
            self.realViewport.call(gl, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

            // If the backbuffer has an alpha channel clear every frame so the page
            // doesn't show through.
            if (self.ctxAttribs.alpha) {
                self.realClearColor.call(gl, 0, 0, 0, 1);
                gl.clear(gl.COLOR_BUFFER_BIT);
            }

            // Bind distortion program and mesh
            gl.useProgram(self.program);

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.indexBuffer);

            gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer);
            gl.enableVertexAttribArray(self.attribs.position);
            gl.enableVertexAttribArray(self.attribs.texCoord);
            gl.vertexAttribPointer(self.attribs.position, 2, gl.FLOAT, false, 20, 0);
            gl.vertexAttribPointer(self.attribs.texCoord, 3, gl.FLOAT, false, 20, 8);

            gl.activeTexture(gl.TEXTURE0);
            gl.uniform1i(self.uniforms.diffuse, 0);
            gl.bindTexture(gl.TEXTURE_2D, self.renderTarget);

            gl.uniform4fv(self.uniforms.viewportOffsetScale, self.viewportOffsetScale);

            // Draws both eyes
            gl.drawElements(gl.TRIANGLES, self.indexCount, gl.UNSIGNED_SHORT, 0);

            self.cardboardUI.renderNoState();

            // Bind the fake default framebuffer again
            self.realBindFramebuffer.call(self.gl, gl.FRAMEBUFFER, self.framebuffer);

            // If preserveDrawingBuffer == false clear the framebuffer
            if (!self.ctxAttribs.preserveDrawingBuffer) {
                self.realClearColor.call(gl, 0, 0, 0, 0);
                gl.clear(gl.COLOR_BUFFER_BIT);
            }

            if (!WebVRConfig.DIRTY_SUBMIT_FRAME_BINDINGS) {
                self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.lastBoundFramebuffer);
            }

            // Restore state
            if (self.cullFace) { self.realEnable.call(gl, gl.CULL_FACE); }
            if (self.depthTest) { self.realEnable.call(gl, gl.DEPTH_TEST); }
            if (self.blend) { self.realEnable.call(gl, gl.BLEND); }
            if (self.scissorTest) { self.realEnable.call(gl, gl.SCISSOR_TEST); }
            if (self.stencilTest) { self.realEnable.call(gl, gl.STENCIL_TEST); }

            self.realColorMask.apply(gl, self.colorMask);
            self.realViewport.apply(gl, self.viewport);
            if (self.ctxAttribs.alpha || !self.ctxAttribs.preserveDrawingBuffer) {
                self.realClearColor.apply(gl, self.clearColor);
            }
        });
    };

    /**
     * Call when the deviceInfo has changed. At this point we need
     * to re-calculate the distortion mesh.
     */
    CardboardDistorter.prototype.updateDeviceInfo = function(deviceInfo) {
        var gl = this.gl;
        var self = this;

        var glState = [gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING];
        WGLUPreserveGLState(gl, glState, function(gl) {
            var vertices = self.computeMeshVertices_(self.meshWidth, self.meshHeight, deviceInfo);
            gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

            // Indices don't change based on device parameters, so only compute once.
            if (!self.indexCount) {
                var indices = self.computeMeshIndices_(self.meshWidth, self.meshHeight);
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.indexBuffer);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
                self.indexCount = indices.length;
            }
        });
    };

    /**
     * Build the distortion mesh vertices.
     * Based on code from the Unity cardboard plugin.
     */
    CardboardDistorter.prototype.computeMeshVertices_ = function(width, height, deviceInfo) {
        var vertices = new Float32Array(2 * width * height * 5);

        var lensFrustum = deviceInfo.getLeftEyeVisibleTanAngles();
        var noLensFrustum = deviceInfo.getLeftEyeNoLensTanAngles();
        var viewport = deviceInfo.getLeftEyeVisibleScreenRect(noLensFrustum);
        var vidx = 0;
        var iidx = 0;
        for (var e = 0; e < 2; e++) {
            for (var j = 0; j < height; j++) {
                for (var i = 0; i < width; i++, vidx++) {
                    var u = i / (width - 1);
                    var v = j / (height - 1);

                    // Grid points regularly spaced in StreoScreen, and barrel distorted in
                    // the mesh.
                    var s = u;
                    var t = v;
                    var x = Util.lerp(lensFrustum[0], lensFrustum[2], u);
                    var y = Util.lerp(lensFrustum[3], lensFrustum[1], v);
                    var d = Math.sqrt(x * x + y * y);
                    var r = deviceInfo.distortion.distortInverse(d);
                    var p = x * r / d;
                    var q = y * r / d;
                    u = (p - noLensFrustum[0]) / (noLensFrustum[2] - noLensFrustum[0]);
                    v = (q - noLensFrustum[3]) / (noLensFrustum[1] - noLensFrustum[3]);

                    // Convert u,v to mesh screen coordinates.
                    var aspect = deviceInfo.device.widthMeters / deviceInfo.device.heightMeters;

                    // FIXME: The original Unity plugin multiplied U by the aspect ratio
                    // and didn't multiply either value by 2, but that seems to get it
                    // really close to correct looking for me. I hate this kind of "Don't
                    // know why it works" code though, and wold love a more logical
                    // explanation of what needs to happen here.
                    u = (viewport.x + u * viewport.width - 0.5) * 2.0; //* aspect;
                    v = (viewport.y + v * viewport.height - 0.5) * 2.0;

                    vertices[(vidx * 5) + 0] = u; // position.x
                    vertices[(vidx * 5) + 1] = v; // position.y
                    vertices[(vidx * 5) + 2] = s; // texCoord.x
                    vertices[(vidx * 5) + 3] = t; // texCoord.y
                    vertices[(vidx * 5) + 4] = e; // texCoord.z (viewport index)
                }
            }
            var w = lensFrustum[2] - lensFrustum[0];
            lensFrustum[0] = -(w + lensFrustum[0]);
            lensFrustum[2] = w - lensFrustum[2];
            w = noLensFrustum[2] - noLensFrustum[0];
            noLensFrustum[0] = -(w + noLensFrustum[0]);
            noLensFrustum[2] = w - noLensFrustum[2];
            viewport.x = 1 - (viewport.x + viewport.width);
        }
        return vertices;
    }

    /**
     * Build the distortion mesh indices.
     * Based on code from the Unity cardboard plugin.
     */
    CardboardDistorter.prototype.computeMeshIndices_ = function(width, height) {
        var indices = new Uint16Array(2 * (width - 1) * (height - 1) * 6);
        var halfwidth = width / 2;
        var halfheight = height / 2;
        var vidx = 0;
        var iidx = 0;
        for (var e = 0; e < 2; e++) {
            for (var j = 0; j < height; j++) {
                for (var i = 0; i < width; i++, vidx++) {
                    if (i == 0 || j == 0)
                        continue;
                    // Build a quad.  Lower right and upper left quadrants have quads with
                    // the triangle diagonal flipped to get the vignette to interpolate
                    // correctly.
                    if ((i <= halfwidth) == (j <= halfheight)) {
                        // Quad diagonal lower left to upper right.
                        indices[iidx++] = vidx;
                        indices[iidx++] = vidx - width - 1;
                        indices[iidx++] = vidx - width;
                        indices[iidx++] = vidx - width - 1;
                        indices[iidx++] = vidx;
                        indices[iidx++] = vidx - 1;
                    } else {
                        // Quad diagonal upper left to lower right.
                        indices[iidx++] = vidx - 1;
                        indices[iidx++] = vidx - width;
                        indices[iidx++] = vidx;
                        indices[iidx++] = vidx - width;
                        indices[iidx++] = vidx - 1;
                        indices[iidx++] = vidx - width - 1;
                    }
                }
            }
        }
        return indices;
    }

    module.exports = CardboardDistorter;

},{"./cardboard-ui.js":4,"./deps/wglu-preserve-state.js":6,"./util.js":22}],4:[function(_dereq_,module,exports){
    /*
     * Copyright 2016 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var Util = _dereq_('./util.js');
    var WGLUPreserveGLState = _dereq_('./deps/wglu-preserve-state.js');

    var uiVS = [
        'attribute vec2 position;',

        'uniform mat4 projectionMat;',

        'void main() {',
        '  gl_Position = projectionMat * vec4( position, -1.0, 1.0 );',
        '}',
    ].join('\n');

    var uiFS = [
        'precision mediump float;',

        'uniform vec4 color;',

        'void main() {',
        '  gl_FragColor = color;',
        '}',
    ].join('\n');

    var DEG2RAD = Math.PI/180.0;

// The gear has 6 identical sections, each spanning 60 degrees.
    var kAnglePerGearSection = 60;

// Half-angle of the span of the outer rim.
    var kOuterRimEndAngle = 12;

// Angle between the middle of the outer rim and the start of the inner rim.
    var kInnerRimBeginAngle = 20;

// Distance from center to outer rim, normalized so that the entire model
// fits in a [-1, 1] x [-1, 1] square.
    var kOuterRadius = 1;

// Distance from center to depressed rim, in model units.
    var kMiddleRadius = 0.75;

// Radius of the inner hollow circle, in model units.
    var kInnerRadius = 0.3125;

// Center line thickness in DP.
    var kCenterLineThicknessDp = 4;

// Button width in DP.
    var kButtonWidthDp = 28;

// Factor to scale the touch area that responds to the touch.
    var kTouchSlopFactor = 1.5;

    var Angles = [
        0, kOuterRimEndAngle, kInnerRimBeginAngle,
        kAnglePerGearSection - kInnerRimBeginAngle,
        kAnglePerGearSection - kOuterRimEndAngle
    ];

    /**
     * Renders the alignment line and "options" gear. It is assumed that the canvas
     * this is rendered into covers the entire screen (or close to it.)
     */
    function CardboardUI(gl) {
        this.gl = gl;

        this.attribs = {
            position: 0
        };
        this.program = Util.linkProgram(gl, uiVS, uiFS, this.attribs);
        this.uniforms = Util.getProgramUniforms(gl, this.program);

        this.vertexBuffer = gl.createBuffer();
        this.gearOffset = 0;
        this.gearVertexCount = 0;
        this.arrowOffset = 0;
        this.arrowVertexCount = 0;

        this.projMat = new Float32Array(16);

        this.listener = null;

        this.onResize();
    };

    /**
     * Tears down all the resources created by the UI renderer.
     */
    CardboardUI.prototype.destroy = function() {
        var gl = this.gl;

        if (this.listener)
            gl.canvas.removeEventListener('click', this.listener, false);

        gl.deleteProgram(this.program);
        gl.deleteBuffer(this.vertexBuffer);
    };

    /**
     * Adds a listener to clicks on the gear and back icons
     */
    CardboardUI.prototype.listen = function(optionsCallback, backCallback) {
        var canvas = this.gl.canvas;
        this.listener = function(event) {
            var midline = canvas.clientWidth / 2;
            var buttonSize = kButtonWidthDp * kTouchSlopFactor;
            // Check to see if the user clicked on (or around) the gear icon
            if (event.clientX > midline - buttonSize &&
                event.clientX < midline + buttonSize &&
                event.clientY > canvas.clientHeight - buttonSize) {
                optionsCallback(event);
            }
            // Check to see if the user clicked on (or around) the back icon
            else if (event.clientX < buttonSize && event.clientY < buttonSize) {
                backCallback(event);
            }
        };
        canvas.addEventListener('click', this.listener, false);
    };

    /**
     * Builds the UI mesh.
     */
    CardboardUI.prototype.onResize = function() {
        var gl = this.gl;
        var self = this;

        var glState = [
            gl.ARRAY_BUFFER_BINDING
        ];

        WGLUPreserveGLState(gl, glState, function(gl) {
            var vertices = [];

            var midline = gl.drawingBufferWidth/2;

            // Assumes your canvas width and height is scaled proportionately.
            var dps = window.devicePixelRatio * (gl.drawingBufferWidth / (screen.width*window.devicePixelRatio));

            var lineWidth = kCenterLineThicknessDp * dps / 2;
            var buttonSize = kButtonWidthDp * kTouchSlopFactor * dps;
            var buttonScale = kButtonWidthDp * dps / 2;
            var buttonBorder = ((kButtonWidthDp * kTouchSlopFactor) - kButtonWidthDp) * dps;

            // Build centerline
            vertices.push(midline - lineWidth, buttonSize);
            vertices.push(midline - lineWidth, gl.drawingBufferHeight);
            vertices.push(midline + lineWidth, buttonSize);
            vertices.push(midline + lineWidth, gl.drawingBufferHeight);

            // Build gear
            self.gearOffset = (vertices.length / 2);

            function addGearSegment(theta, r) {
                var angle = (90 - theta) * DEG2RAD;
                var x = Math.cos(angle);
                var y = Math.sin(angle);
                vertices.push(kInnerRadius * x * buttonScale + midline, kInnerRadius * y * buttonScale + buttonScale);
                vertices.push(r * x * buttonScale + midline, r * y * buttonScale + buttonScale);
            }

            for (var i = 0; i <= 6; i++) {
                var segmentTheta = i * kAnglePerGearSection;

                addGearSegment(segmentTheta, kOuterRadius);
                addGearSegment(segmentTheta + kOuterRimEndAngle, kOuterRadius);
                addGearSegment(segmentTheta + kInnerRimBeginAngle, kMiddleRadius);
                addGearSegment(segmentTheta + (kAnglePerGearSection - kInnerRimBeginAngle), kMiddleRadius);
                addGearSegment(segmentTheta + (kAnglePerGearSection - kOuterRimEndAngle), kOuterRadius);
            }

            self.gearVertexCount = (vertices.length / 2) - self.gearOffset;

            // Build back arrow
            self.arrowOffset = (vertices.length / 2);

            function addArrowVertex(x, y) {
                vertices.push(buttonBorder + x, gl.drawingBufferHeight - buttonBorder - y);
            }

            var angledLineWidth = lineWidth / Math.sin(45 * DEG2RAD);

            addArrowVertex(0, buttonScale);
            addArrowVertex(buttonScale, 0);
            addArrowVertex(buttonScale + angledLineWidth, angledLineWidth);
            addArrowVertex(angledLineWidth, buttonScale + angledLineWidth);

            addArrowVertex(angledLineWidth, buttonScale - angledLineWidth);
            addArrowVertex(0, buttonScale);
            addArrowVertex(buttonScale, buttonScale * 2);
            addArrowVertex(buttonScale + angledLineWidth, (buttonScale * 2) - angledLineWidth);

            addArrowVertex(angledLineWidth, buttonScale - angledLineWidth);
            addArrowVertex(0, buttonScale);

            addArrowVertex(angledLineWidth, buttonScale - lineWidth);
            addArrowVertex(kButtonWidthDp * dps, buttonScale - lineWidth);
            addArrowVertex(angledLineWidth, buttonScale + lineWidth);
            addArrowVertex(kButtonWidthDp * dps, buttonScale + lineWidth);

            self.arrowVertexCount = (vertices.length / 2) - self.arrowOffset;

            // Buffer data
            gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);


        });
    };

    /**
     * Performs distortion pass on the injected backbuffer, rendering it to the real
     * backbuffer.
     */
    CardboardUI.prototype.render = function() {
        var gl = this.gl;
        var self = this;

        var glState = [
            gl.CULL_FACE,
            gl.DEPTH_TEST,
            gl.BLEND,
            gl.SCISSOR_TEST,
            gl.STENCIL_TEST,
            gl.COLOR_WRITEMASK,
            gl.VIEWPORT,

            gl.CURRENT_PROGRAM,
            gl.ARRAY_BUFFER_BINDING
        ];

        WGLUPreserveGLState(gl, glState, function(gl) {
            // Make sure the GL state is in a good place
            gl.disable(gl.CULL_FACE);
            gl.disable(gl.DEPTH_TEST);
            gl.disable(gl.BLEND);
            gl.disable(gl.SCISSOR_TEST);
            gl.disable(gl.STENCIL_TEST);
            gl.colorMask(true, true, true, true);
            gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

            self.renderNoState();
        });
    };

    CardboardUI.prototype.renderNoState = function() {
        var gl = this.gl;

        // Bind distortion program and mesh
        gl.useProgram(this.program);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
        gl.enableVertexAttribArray(this.attribs.position);
        gl.vertexAttribPointer(this.attribs.position, 2, gl.FLOAT, false, 8, 0);

        gl.uniform4f(this.uniforms.color, 1.0, 1.0, 1.0, 1.0);

        Util.orthoMatrix(this.projMat, 0, gl.drawingBufferWidth, 0, gl.drawingBufferHeight, 0.1, 1024.0);
        gl.uniformMatrix4fv(this.uniforms.projectionMat, false, this.projMat);

        // Draws UI element
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        gl.drawArrays(gl.TRIANGLE_STRIP, this.gearOffset, this.gearVertexCount);
        gl.drawArrays(gl.TRIANGLE_STRIP, this.arrowOffset, this.arrowVertexCount);
    };

    module.exports = CardboardUI;
},{"./deps/wglu-preserve-state.js":6,"./util.js":22}],5:[function(_dereq_,module,exports){
    /*
     * Copyright 2016 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var CardboardDistorter = _dereq_('./cardboard-distorter.js');
    var DeviceInfo = _dereq_('./device-info.js');
    var Dpdb = _dereq_('./dpdb/dpdb.js');
    var FusionPoseSensor = _dereq_('./sensor-fusion/fusion-pose-sensor.js');
    var RotateInstructions = _dereq_('./rotate-instructions.js');
    var ViewerSelector = _dereq_('./viewer-selector.js');
    var VRDisplay = _dereq_('./base.js').VRDisplay;
    var Util = _dereq_('./util.js');

    var Eye = {
        LEFT: 'left',
        RIGHT: 'right'
    };

    /**
     * VRDisplay based on mobile device parameters and DeviceMotion APIs.
     */
    function CardboardVRDisplay() {
        this.displayName = 'Cardboard VRDisplay (webvr-polyfill)';

        this.capabilities.hasOrientation = true;
        this.capabilities.canPresent = true;

        // "Private" members.
        this.bufferScale_ = WebVRConfig.BUFFER_SCALE ? WebVRConfig.BUFFER_SCALE : 1.0;
        this.poseSensor_ = new FusionPoseSensor();
        this.distorter_ = null;
        this.cardboardUI_ = null;

        this.dpdb_ = new Dpdb(true, this.onDeviceParamsUpdated_.bind(this));
        this.deviceInfo_ = new DeviceInfo(this.dpdb_.getDeviceParams());

        this.viewerSelector_ = new ViewerSelector();
        this.viewerSelector_.on('change', this.onViewerChanged_.bind(this));

        // Set the correct initial viewer.
        this.deviceInfo_.setViewer(this.viewerSelector_.getCurrentViewer());

        this.rotateInstructions_ = new RotateInstructions();
    }
    CardboardVRDisplay.prototype = new VRDisplay();

    CardboardVRDisplay.prototype.getImmediatePose = function() {
        return {
            position: this.poseSensor_.getPosition(),
            orientation: this.poseSensor_.getOrientation(),
            linearVelocity: null,
            linearAcceleration: null,
            angularVelocity: null,
            angularAcceleration: null
        };
    };

    CardboardVRDisplay.prototype.resetPose = function() {
        this.poseSensor_.resetPose();
    };

    CardboardVRDisplay.prototype.getEyeParameters = function(whichEye) {
        var offset = [this.deviceInfo_.viewer.interLensDistance * 0.5, 0.0, 0.0];
        var fieldOfView;

        // TODO: FoV can be a little expensive to compute. Cache when device params change.
        if (whichEye == Eye.LEFT) {
            offset[0] *= -1.0;
            fieldOfView = this.deviceInfo_.getFieldOfViewLeftEye();
        } else if (whichEye == Eye.RIGHT) {
            fieldOfView = this.deviceInfo_.getFieldOfViewRightEye();
        } else {
            console.error('Invalid eye provided: %s', whichEye);
            return null;
        }

        return {
            fieldOfView: fieldOfView,
            offset: offset,
            // TODO: Should be able to provide better values than these.
            renderWidth: this.deviceInfo_.device.width * 0.5 * this.bufferScale_,
            renderHeight: this.deviceInfo_.device.height * this.bufferScale_,
        };
    };

    CardboardVRDisplay.prototype.onDeviceParamsUpdated_ = function(newParams) {
        console.log('DPDB reported that device params were updated.');
        this.deviceInfo_.updateDeviceParams(newParams);

        if (this.distorter_) {
            this.distorter.updateDeviceInfo(this.deviceInfo_);
        }
    };

    CardboardVRDisplay.prototype.beginPresent_ = function() {
        var gl = this.layer_.source.getContext('webgl');
        if (!gl)
            gl = this.layer_.source.getContext('experimental-webgl');
        if (!gl)
            gl = this.layer_.source.getContext('webgl2');

        if (!gl)
            return; // Can't do distortion without a WebGL context.

        // Provides a way to opt out of distortion
        if (this.layer_.predistorted) {
            this.cardboardUI_ = new CardboardUI(gl);
        } else {
            // Create a new distorter for the target context
            this.distorter_ = new CardboardDistorter(gl);
            this.distorter_.updateDeviceInfo(this.deviceInfo_);
            this.cardboardUI_ = this.distorter_.cardboardUI;

            if (this.layer_.leftBounds || this.layer_.rightBounds) {
                this.distorter_.setTextureBounds(this.layer_.leftBounds, this.layer_.rightBounds);
            }
        }

        this.cardboardUI_.listen(function() {
            // Options clicked
            this.viewerSelector_.show(this.layer_.source.parentElement);
        }.bind(this), function() {
            // Back clicked
            this.exitPresent();
        }.bind(this));

        if (!Util.isLandscapeMode() && Util.isMobile()) {
            // In landscape mode, temporarily show the "put into Cardboard"
            // interstitial. Otherwise, do the default thing.
            this.rotateInstructions_.showTemporarily(3000, this.fullscreenWrapper_);
        } else {
            this.rotateInstructions_.update();
        }

        // Listen for orientation change events in order to show interstitial.
        this.orientationHandler = this.onOrientationChange_.bind(this);
        window.addEventListener('orientationchange', this.orientationHandler);

        // Fire this event initially, to give geometry-distortion clients the chance
        // to do something custom.
        this.fireVRDisplayDeviceParamsChange_();
    };

    CardboardVRDisplay.prototype.endPresent_ = function() {
        if (this.distorter_) {
            this.distorter_.destroy();
            this.distorter_ = null;
        }

        this.rotateInstructions_.hide();
        this.viewerSelector_.hide();

        window.removeEventListener('orientationchange', this.orientationHandler);
    };

    CardboardVRDisplay.prototype.submitFrame = function(pose) {
        if (this.distorter_) {
            this.distorter_.submitFrame();
        } else if (this.cardboardUI_) {
            this.cardboardUI_.render();
        }
    };

    CardboardVRDisplay.prototype.onOrientationChange_ = function(e) {
        console.log('onOrientationChange_');

        // Hide the viewer selector.
        this.viewerSelector_.hide();

        // Update the rotate instructions.
        this.rotateInstructions_.update();
    };

    CardboardVRDisplay.prototype.onViewerChanged_ = function(viewer) {
        this.deviceInfo_.setViewer(viewer);

        // Update the distortion appropriately.
        this.distorter_.updateDeviceInfo(this.deviceInfo_);

        // Fire a new event containing viewer and device parameters for clients that
        // want to implement their own geometry-based distortion.
        this.fireVRDisplayDeviceParamsChange_();
    };

    CardboardVRDisplay.prototype.fireVRDisplayDeviceParamsChange_ = function() {
        var event = new CustomEvent('vrdisplaydeviceparamschange', {
            detail: {
                vrdisplay: this,
                deviceInfo: this.deviceInfo_,
            }
        });
        window.dispatchEvent(event);
    };

    module.exports = CardboardVRDisplay;

},{"./base.js":2,"./cardboard-distorter.js":3,"./device-info.js":7,"./dpdb/dpdb.js":11,"./rotate-instructions.js":15,"./sensor-fusion/fusion-pose-sensor.js":17,"./util.js":22,"./viewer-selector.js":23}],6:[function(_dereq_,module,exports){
    /*
     Copyright (c) 2016, Brandon Jones.

     Permission is hereby granted, free of charge, to any person obtaining a copy
     of this software and associated documentation files (the "Software"), to deal
     in the Software without restriction, including without limitation the rights
     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     copies of the Software, and to permit persons to whom the Software is
     furnished to do so, subject to the following conditions:

     The above copyright notice and this permission notice shall be included in
     all copies or substantial portions of the Software.

     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     THE SOFTWARE.
     */

    /*
     Caches specified GL state, runs a callback, and restores the cached state when
     done.

     Example usage:

     var savedState = [
     gl.ARRAY_BUFFER_BINDING,

     // TEXTURE_BINDING_2D or _CUBE_MAP must always be followed by the texure unit.
     gl.TEXTURE_BINDING_2D, gl.TEXTURE0,

     gl.CLEAR_COLOR,
     ];
     // After this call the array buffer, texture unit 0, active texture, and clear
     // color will be restored. The viewport will remain changed, however, because
     // gl.VIEWPORT was not included in the savedState list.
     WGLUPreserveGLState(gl, savedState, function(gl) {
     gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

     gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
     gl.bufferData(gl.ARRAY_BUFFER, ....);

     gl.activeTexture(gl.TEXTURE0);
     gl.bindTexture(gl.TEXTURE_2D, texture);
     gl.texImage2D(gl.TEXTURE_2D, ...);

     gl.clearColor(1, 0, 0, 1);
     gl.clear(gl.COLOR_BUFFER_BIT);
     });

     Note that this is not intended to be fast. Managing state in your own code to
     avoid redundant state setting and querying will always be faster. This function
     is most useful for cases where you may not have full control over the WebGL
     calls being made, such as tooling or effect injectors.
     */

    function WGLUPreserveGLState(gl, bindings, callback) {
        if (!bindings) {
            callback(gl);
            return;
        }

        var boundValues = [];

        var activeTexture = null;
        for (var i = 0; i < bindings.length; ++i) {
            var binding = bindings[i];
            switch (binding) {
                case gl.TEXTURE_BINDING_2D:
                case gl.TEXTURE_BINDING_CUBE_MAP:
                    var textureUnit = bindings[++i];
                    if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) {
                        console.error("TEXTURE_BINDING_2D or TEXTURE_BINDING_CUBE_MAP must be followed by a valid texture unit");
                        boundValues.push(null, null);
                        break;
                    }
                    if (!activeTexture) {
                        activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE);
                    }
                    gl.activeTexture(textureUnit);
                    boundValues.push(gl.getParameter(binding), null);
                    break;
                case gl.ACTIVE_TEXTURE:
                    activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE);
                    boundValues.push(null);
                    break;
                default:
                    boundValues.push(gl.getParameter(binding));
                    break;
            }
        }

        callback(gl);

        for (var i = 0; i < bindings.length; ++i) {
            var binding = bindings[i];
            var boundValue = boundValues[i];
            switch (binding) {
                case gl.ACTIVE_TEXTURE:
                    break; // Ignore this binding, since we special-case it to happen last.
                case gl.ARRAY_BUFFER_BINDING:
                    gl.bindBuffer(gl.ARRAY_BUFFER, boundValue);
                    break;
                case gl.COLOR_CLEAR_VALUE:
                    gl.clearColor(boundValue[0], boundValue[1], boundValue[2], boundValue[3]);
                    break;
                case gl.COLOR_WRITEMASK:
                    gl.colorMask(boundValue[0], boundValue[1], boundValue[2], boundValue[3]);
                    break;
                case gl.CURRENT_PROGRAM:
                    gl.useProgram(boundValue);
                    break;
                case gl.ELEMENT_ARRAY_BUFFER_BINDING:
                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, boundValue);
                    break;
                case gl.FRAMEBUFFER_BINDING:
                    gl.bindFramebuffer(gl.FRAMEBUFFER, boundValue);
                    break;
                case gl.RENDERBUFFER_BINDING:
                    gl.bindRenderbuffer(gl.RENDERBUFFER, boundValue);
                    break;
                case gl.TEXTURE_BINDING_2D:
                    var textureUnit = bindings[++i];
                    if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31)
                        break;
                    gl.activeTexture(textureUnit);
                    gl.bindTexture(gl.TEXTURE_2D, boundValue);
                    break;
                case gl.TEXTURE_BINDING_CUBE_MAP:
                    var textureUnit = bindings[++i];
                    if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31)
                        break;
                    gl.activeTexture(textureUnit);
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, boundValue);
                    break;
                case gl.VIEWPORT:
                    gl.viewport(boundValue[0], boundValue[1], boundValue[2], boundValue[3]);
                    break;
                case gl.BLEND:
                case gl.CULL_FACE:
                case gl.DEPTH_TEST:
                case gl.SCISSOR_TEST:
                case gl.STENCIL_TEST:
                    if (boundValue) {
                        gl.enable(binding);
                    } else {
                        gl.disable(binding);
                    }
                    break;
                default:
                    console.log("No GL restore behavior for 0x" + binding.toString(16));
                    break;
            }

            if (activeTexture) {
                gl.activeTexture(activeTexture);
            }
        }
    }

    module.exports = WGLUPreserveGLState;
},{}],7:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var Distortion = _dereq_('./distortion/distortion.js');
    var THREE = _dereq_('./three-math.js');
    var Util = _dereq_('./util.js');

    function Device(params) {
        this.width = params.width || Util.getScreenWidth();
        this.height = params.height || Util.getScreenHeight();
        this.widthMeters = params.widthMeters;
        this.heightMeters = params.heightMeters;
        this.bevelMeters = params.bevelMeters;
    }


// Fallback Android device (based on Nexus 5 measurements) for use when
// we can't recognize an Android device.
    var DEFAULT_ANDROID = new Device({
        widthMeters: 0.110,
        heightMeters: 0.062,
        bevelMeters: 0.004
    });

// Fallback iOS device (based on iPhone6) for use when
// we can't recognize an Android device.
    var DEFAULT_IOS = new Device({
        widthMeters: 0.1038,
        heightMeters: 0.0584,
        bevelMeters: 0.004
    });


    var Viewers = {
        CardboardV1: new CardboardViewer({
            id: 'CardboardV1',
            label: 'Cardboard I/O 2014',
            fov: 40,
            interLensDistance: 0.060,
            baselineLensDistance: 0.035,
            screenLensDistance: 0.042,
            distortionCoefficients: [0.441, 0.156],
            inverseCoefficients: [-0.4410035, 0.42756155, -0.4804439, 0.5460139,
                -0.58821183, 0.5733938, -0.48303202, 0.33299083, -0.17573841,
                0.0651772, -0.01488963, 0.001559834]
        }),
        CardboardV2: new CardboardViewer({
            id: 'CardboardV2',
            label: 'Cardboard I/O 2015',
            fov: 60,
            interLensDistance: 0.064,
            baselineLensDistance: 0.035,
            screenLensDistance: 0.039,
            distortionCoefficients: [0.34, 0.55],
            inverseCoefficients: [-0.33836704, -0.18162185, 0.862655, -1.2462051,
                1.0560602, -0.58208317, 0.21609078, -0.05444823, 0.009177956,
                -9.904169E-4, 6.183535E-5, -1.6981803E-6]
        })
    };


    var DEFAULT_LEFT_CENTER = {x: 0.5, y: 0.5};
    var DEFAULT_RIGHT_CENTER = {x: 0.5, y: 0.5};

    /**
     * Manages information about the device and the viewer.
     *
     * deviceParams indicates the parameters of the device to use (generally
     * obtained from dpdb.getDeviceParams()). Can be null to mean no device
     * params were found.
     */
    function DeviceInfo(deviceParams) {
        this.viewer = Viewers.CardboardV2;
        this.updateDeviceParams(deviceParams);
        this.distortion = new Distortion(this.viewer.distortionCoefficients);
    }

    DeviceInfo.prototype.updateDeviceParams = function(deviceParams) {
        this.device = this.determineDevice_(deviceParams) || this.device;
    };

    DeviceInfo.prototype.getDevice = function() {
        return this.device;
    };

    DeviceInfo.prototype.setViewer = function(viewer) {
        this.viewer = viewer;
        this.distortion = new Distortion(this.viewer.distortionCoefficients);
    };

    DeviceInfo.prototype.determineDevice_ = function(deviceParams) {
        if (!deviceParams) {
            // No parameters, so use a default.
            if (Util.isIOS()) {
                console.warn('Using fallback Android device measurements.');
                return DEFAULT_IOS;
            } else {
                console.warn('Using fallback iOS device measurements.');
                return DEFAULT_ANDROID;
            }
        }

        // Compute device screen dimensions based on deviceParams.
        var METERS_PER_INCH = 0.0254;
        var metersPerPixelX = METERS_PER_INCH / deviceParams.xdpi;
        var metersPerPixelY = METERS_PER_INCH / deviceParams.ydpi;
        var width = Util.getScreenWidth();
        var height = Util.getScreenHeight();
        return new Device({
            widthMeters: metersPerPixelX * width,
            heightMeters: metersPerPixelY * height,
            bevelMeters: deviceParams.bevelMm * 0.001,
        });
    };

    /**
     * Calculates field of view for the left eye.
     */
    DeviceInfo.prototype.getDistortedFieldOfViewLeftEye = function() {
        var viewer = this.viewer;
        var device = this.device;
        var distortion = this.distortion;

        // Device.height and device.width for device in portrait mode, so transpose.
        var eyeToScreenDistance = viewer.screenLensDistance;

        var outerDist = (device.widthMeters - viewer.interLensDistance) / 2;
        var innerDist = viewer.interLensDistance / 2;
        var bottomDist = viewer.baselineLensDistance - device.bevelMeters;
        var topDist = device.heightMeters - bottomDist;

        var outerAngle = THREE.Math.radToDeg(Math.atan(
            distortion.distort(outerDist / eyeToScreenDistance)));
        var innerAngle = THREE.Math.radToDeg(Math.atan(
            distortion.distort(innerDist / eyeToScreenDistance)));
        var bottomAngle = THREE.Math.radToDeg(Math.atan(
            distortion.distort(bottomDist / eyeToScreenDistance)));
        var topAngle = THREE.Math.radToDeg(Math.atan(
            distortion.distort(topDist / eyeToScreenDistance)));

        return {
            leftDegrees: Math.min(outerAngle, viewer.fov),
            rightDegrees: Math.min(innerAngle, viewer.fov),
            downDegrees: Math.min(bottomAngle, viewer.fov),
            upDegrees: Math.min(topAngle, viewer.fov)
        };
    };

    /**
     * Calculates the tan-angles from the maximum FOV for the left eye for the
     * current device and screen parameters.
     */
    DeviceInfo.prototype.getLeftEyeVisibleTanAngles = function() {
        var viewer = this.viewer;
        var device = this.device;
        var distortion = this.distortion;

        // Tan-angles from the max FOV.
        var fovLeft = Math.tan(-THREE.Math.degToRad(viewer.fov));
        var fovTop = Math.tan(THREE.Math.degToRad(viewer.fov));
        var fovRight = Math.tan(THREE.Math.degToRad(viewer.fov));
        var fovBottom = Math.tan(-THREE.Math.degToRad(viewer.fov));
        // Viewport size.
        var halfWidth = device.widthMeters / 4;
        var halfHeight = device.heightMeters / 2;
        // Viewport center, measured from left lens position.
        var verticalLensOffset = (viewer.baselineLensDistance - device.bevelMeters - halfHeight);
        var centerX = viewer.interLensDistance / 2 - halfWidth;
        var centerY = -verticalLensOffset;
        var centerZ = viewer.screenLensDistance;
        // Tan-angles of the viewport edges, as seen through the lens.
        var screenLeft = distortion.distort((centerX - halfWidth) / centerZ);
        var screenTop = distortion.distort((centerY + halfHeight) / centerZ);
        var screenRight = distortion.distort((centerX + halfWidth) / centerZ);
        var screenBottom = distortion.distort((centerY - halfHeight) / centerZ);
        // Compare the two sets of tan-angles and take the value closer to zero on each side.
        var result = new Float32Array(4);
        result[0] = Math.max(fovLeft, screenLeft);
        result[1] = Math.min(fovTop, screenTop);
        result[2] = Math.min(fovRight, screenRight);
        result[3] = Math.max(fovBottom, screenBottom);
        return result;
    };

    /**
     * Calculates the tan-angles from the maximum FOV for the left eye for the
     * current device and screen parameters, assuming no lenses.
     */
    DeviceInfo.prototype.getLeftEyeNoLensTanAngles = function() {
        var viewer = this.viewer;
        var device = this.device;
        var distortion = this.distortion;

        var result = new Float32Array(4);
        // Tan-angles from the max FOV.
        var fovLeft = distortion.distortInverse(Math.tan(-THREE.Math.degToRad(viewer.fov)));
        var fovTop = distortion.distortInverse(Math.tan(THREE.Math.degToRad(viewer.fov)));
        var fovRight = distortion.distortInverse(Math.tan(THREE.Math.degToRad(viewer.fov)));
        var fovBottom = distortion.distortInverse(Math.tan(-THREE.Math.degToRad(viewer.fov)));
        // Viewport size.
        var halfWidth = device.widthMeters / 4;
        var halfHeight = device.heightMeters / 2;
        // Viewport center, measured from left lens position.
        var verticalLensOffset = (viewer.baselineLensDistance - device.bevelMeters - halfHeight);
        var centerX = viewer.interLensDistance / 2 - halfWidth;
        var centerY = -verticalLensOffset;
        var centerZ = viewer.screenLensDistance;
        // Tan-angles of the viewport edges, as seen through the lens.
        var screenLeft = (centerX - halfWidth) / centerZ;
        var screenTop = (centerY + halfHeight) / centerZ;
        var screenRight = (centerX + halfWidth) / centerZ;
        var screenBottom = (centerY - halfHeight) / centerZ;
        // Compare the two sets of tan-angles and take the value closer to zero on each side.
        result[0] = Math.max(fovLeft, screenLeft);
        result[1] = Math.min(fovTop, screenTop);
        result[2] = Math.min(fovRight, screenRight);
        result[3] = Math.max(fovBottom, screenBottom);
        return result;
    };

    /**
     * Calculates the screen rectangle visible from the left eye for the
     * current device and screen parameters.
     */
    DeviceInfo.prototype.getLeftEyeVisibleScreenRect = function(undistortedFrustum) {
        var viewer = this.viewer;
        var device = this.device;

        var dist = viewer.screenLensDistance;
        var eyeX = (device.widthMeters - viewer.interLensDistance) / 2;
        var eyeY = viewer.baselineLensDistance - device.bevelMeters;
        var left = (undistortedFrustum[0] * dist + eyeX) / device.widthMeters;
        var top = (undistortedFrustum[1] * dist + eyeY) / device.heightMeters;
        var right = (undistortedFrustum[2] * dist + eyeX) / device.widthMeters;
        var bottom = (undistortedFrustum[3] * dist + eyeY) / device.heightMeters;
        return {
            x: left,
            y: bottom,
            width: right - left,
            height: top - bottom
        };
    };

    DeviceInfo.prototype.getFieldOfViewLeftEye = function(opt_isUndistorted) {
        return opt_isUndistorted ? this.getUndistortedFieldOfViewLeftEye() :
            this.getDistortedFieldOfViewLeftEye();
    };

    DeviceInfo.prototype.getFieldOfViewRightEye = function(opt_isUndistorted) {
        var fov = this.getFieldOfViewLeftEye(opt_isUndistorted);
        return {
            leftDegrees: fov.rightDegrees,
            rightDegrees: fov.leftDegrees,
            upDegrees: fov.upDegrees,
            downDegrees: fov.downDegrees
        };
    };

    /**
     * Calculates a projection matrix for the left eye.
     */
    DeviceInfo.prototype.getProjectionMatrixLeftEye = function(opt_isUndistorted) {
        var fov = this.getFieldOfViewLeftEye(opt_isUndistorted);

        var projectionMatrix = new THREE.Matrix4();
        var near = 0.1;
        var far = 1000;
        var left = Math.tan(THREE.Math.degToRad(fov.leftDegrees)) * near;
        var right = Math.tan(THREE.Math.degToRad(fov.rightDegrees)) * near;
        var bottom = Math.tan(THREE.Math.degToRad(fov.downDegrees)) * near;
        var top = Math.tan(THREE.Math.degToRad(fov.upDegrees)) * near;

        // makeFrustum expects units in tan-angle space.
        projectionMatrix.makeFrustum(-left, right, -bottom, top, near, far);

        return projectionMatrix;
    };


    DeviceInfo.prototype.getUndistortedViewportLeftEye = function() {
        var p = this.getUndistortedParams_();
        var viewer = this.viewer;
        var device = this.device;

        var eyeToScreenDistance = viewer.screenLensDistance;
        var screenWidth = device.widthMeters / eyeToScreenDistance;
        var screenHeight = device.heightMeters / eyeToScreenDistance;
        var xPxPerTanAngle = device.width / screenWidth;
        var yPxPerTanAngle = device.height / screenHeight;

        var x = Math.round((p.eyePosX - p.outerDist) * xPxPerTanAngle);
        var y = Math.round((p.eyePosY - p.bottomDist) * yPxPerTanAngle);
        return {
            x: x,
            y: y,
            width: Math.round((p.eyePosX + p.innerDist) * xPxPerTanAngle) - x,
            height: Math.round((p.eyePosY + p.topDist) * yPxPerTanAngle) - y
        };
    };

    /**
     * Calculates undistorted field of view for the left eye.
     */
    DeviceInfo.prototype.getUndistortedFieldOfViewLeftEye = function() {
        var p = this.getUndistortedParams_();

        return {
            leftDegrees: THREE.Math.radToDeg(Math.atan(p.outerDist)),
            rightDegrees: THREE.Math.radToDeg(Math.atan(p.innerDist)),
            downDegrees: THREE.Math.radToDeg(Math.atan(p.bottomDist)),
            upDegrees: THREE.Math.radToDeg(Math.atan(p.topDist))
        };
    };

    DeviceInfo.prototype.getUndistortedParams_ = function() {
        var viewer = this.viewer;
        var device = this.device;
        var distortion = this.distortion;

        // Most of these variables in tan-angle units.
        var eyeToScreenDistance = viewer.screenLensDistance;
        var halfLensDistance = viewer.interLensDistance / 2 / eyeToScreenDistance;
        var screenWidth = device.widthMeters / eyeToScreenDistance;
        var screenHeight = device.heightMeters / eyeToScreenDistance;

        var eyePosX = screenWidth / 2 - halfLensDistance;
        var eyePosY = (viewer.baselineLensDistance - device.bevelMeters) / eyeToScreenDistance;

        var maxFov = viewer.fov;
        var viewerMax = distortion.distortInverse(Math.tan(THREE.Math.degToRad(maxFov)));
        var outerDist = Math.min(eyePosX, viewerMax);
        var innerDist = Math.min(halfLensDistance, viewerMax);
        var bottomDist = Math.min(eyePosY, viewerMax);
        var topDist = Math.min(screenHeight - eyePosY, viewerMax);

        return {
            outerDist: outerDist,
            innerDist: innerDist,
            topDist: topDist,
            bottomDist: bottomDist,
            eyePosX: eyePosX,
            eyePosY: eyePosY
        };
    };


    function CardboardViewer(params) {
        // A machine readable ID.
        this.id = params.id;
        // A human readable label.
        this.label = params.label;

        // Field of view in degrees (per side).
        this.fov = params.fov;

        // Distance between lens centers in meters.
        this.interLensDistance = params.interLensDistance;
        // Distance between viewer baseline and lens center in meters.
        this.baselineLensDistance = params.baselineLensDistance;
        // Screen-to-lens distance in meters.
        this.screenLensDistance = params.screenLensDistance;

        // Distortion coefficients.
        this.distortionCoefficients = params.distortionCoefficients;
        // Inverse distortion coefficients.
        // TODO: Calculate these from distortionCoefficients in the future.
        this.inverseCoefficients = params.inverseCoefficients;
    }

// Export viewer information.
    DeviceInfo.Viewers = Viewers;
    module.exports = DeviceInfo;
},{"./distortion/distortion.js":9,"./three-math.js":20,"./util.js":22}],8:[function(_dereq_,module,exports){
    /*
     * Copyright 2016 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    var VRDisplay = _dereq_('./base.js').VRDisplay;
    var HMDVRDevice = _dereq_('./base.js').HMDVRDevice;
    var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice;

    /**
     * Wraps a VRDisplay and exposes it as a HMDVRDevice
     */
    function VRDisplayHMDDevice(display) {
        this.display = display;

        this.hardwareUnitId = display.displayId;
        this.deviceId = 'webvr-pollyfill:HMD:' + display.displayId;
        this.deviceName = display.displayName + ' (HMD)';
    }
    VRDisplayHMDDevice.prototype = new HMDVRDevice();

    VRDisplayHMDDevice.prototype.getEyeParameters = function(whichEye) {
        var eyeParameters = this.display.getEyeParameters(whichEye);

        return {
            currentFieldOfView: eyeParameters.fieldOfView,
            maximumFieldOfView: eyeParameters.fieldOfView,
            minimumFieldOfView: eyeParameters.fieldOfView,
            recommendedFieldOfView: eyeParameters.fieldOfView,
            eyeTranslation: { x: eyeParameters.offset[0], y: eyeParameters.offset[1], z: eyeParameters.offset[2] },
            renderRect: {
                x: (whichEye == 'right') ? eyeParameters.renderWidth : 0,
                y: 0,
                width: eyeParameters.renderWidth,
                height: eyeParameters.renderHeight
            }
        };
    };

    VRDisplayHMDDevice.prototype.setFieldOfView =
        function(opt_fovLeft, opt_fovRight, opt_zNear, opt_zFar) {
            // Not supported. getEyeParameters reports that the min, max, and recommended
            // FoV is all the same, so no adjustment can be made.
        };

// TODO: Need to hook requestFullscreen to see if a wrapped VRDisplay was passed
// in as an option. If so we should prevent the default fullscreen behavior and
// call VRDisplay.requestPresent instead.

    /**
     * Wraps a VRDisplay and exposes it as a PositionSensorVRDevice
     */
    function VRDisplayPositionSensorDevice(display) {
        this.display = display;

        this.hardwareUnitId = display.displayId;
        this.deviceId = 'webvr-pollyfill:PositionSensor: ' + display.displayId;
        this.deviceName = display.displayName + ' (PositionSensor)';
    }
    VRDisplayPositionSensorDevice.prototype = new PositionSensorVRDevice();

    VRDisplayPositionSensorDevice.prototype.getState = function() {
        var pose = this.display.getPose();
        return {
            position: pose.position ? { x: pose.position[0], y: pose.position[1], z: pose.position[2] } : null,
            orientation: pose.orientation ? { x: pose.orientation[0], y: pose.orientation[1], z: pose.orientation[2], w: pose.orientation[3] } : null,
            linearVelocity: null,
            linearAcceleration: null,
            angularVelocity: null,
            angularAcceleration: null
        };
    };

    VRDisplayPositionSensorDevice.prototype.resetState = function() {
        return this.positionDevice.resetPose();
    };


    module.exports.VRDisplayHMDDevice = VRDisplayHMDDevice;
    module.exports.VRDisplayPositionSensorDevice = VRDisplayPositionSensorDevice;


},{"./base.js":2}],9:[function(_dereq_,module,exports){
    /**
     * TODO(smus): Implement coefficient inversion.
     */
    function Distortion(coefficients) {
        this.coefficients = coefficients;
    }

    /**
     * Calculates the inverse distortion for a radius.
     * </p><p>
     * Allows to compute the original undistorted radius from a distorted one.
     * See also getApproximateInverseDistortion() for a faster but potentially
     * less accurate method.
     *
     * @param {Number} radius Distorted radius from the lens center in tan-angle units.
     * @return {Number} The undistorted radius in tan-angle units.
     */
    Distortion.prototype.distortInverse = function(radius) {
        // Secant method.
        var r0 = 0;
        var r1 = 1;
        var dr0 = radius - this.distort(r0);
        while (Math.abs(r1 - r0) > 0.0001 /** 0.1mm */) {
            var dr1 = radius - this.distort(r1);
            var r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0));
            r0 = r1;
            r1 = r2;
            dr0 = dr1;
        }
        return r1;
    }

    /**
     * Distorts a radius by its distortion factor from the center of the lenses.
     *
     * @param {Number} radius Radius from the lens center in tan-angle units.
     * @return {Number} The distorted radius in tan-angle units.
     */
    Distortion.prototype.distort = function(radius) {
        var r2 = radius * radius;
        var ret = 0;
        for (var i = 0; i < this.coefficients.length; i++) {
            ret = r2 * (ret + this.coefficients[i]);
        }
        return (ret + 1) * radius;
    }

    module.exports = Distortion;
},{}],10:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /**
     * DPDB cache.
     */
    var DPDB_CACHE = {
        "format": 1,
        "last_updated": "2016-01-20T00:18:35Z",
        "devices": [

            {
                "type": "android",
                "rules": [
                    { "mdmh": "asus/*/Nexus 7/*" },
                    { "ua": "Nexus 7" }
                ],
                "dpi": [ 320.8, 323.0 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "asus/*/ASUS_Z00AD/*" },
                    { "ua": "ASUS_Z00AD" }
                ],
                "dpi": [ 403.0, 404.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "HTC/*/HTC6435LVW/*" },
                    { "ua": "HTC6435LVW" }
                ],
                "dpi": [ 449.7, 443.3 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "HTC/*/HTC One XL/*" },
                    { "ua": "HTC One XL" }
                ],
                "dpi": [ 315.3, 314.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "htc/*/Nexus 9/*" },
                    { "ua": "Nexus 9" }
                ],
                "dpi": 289.0,
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "HTC/*/HTC One M9/*" },
                    { "ua": "HTC One M9" }
                ],
                "dpi": [ 442.5, 443.3 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "HTC/*/HTC One_M8/*" },
                    { "ua": "HTC One_M8" }
                ],
                "dpi": [ 449.7, 447.4 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "HTC/*/HTC One/*" },
                    { "ua": "HTC One" }
                ],
                "dpi": 472.8,
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Huawei/*/Nexus 6P/*" },
                    { "ua": "Nexus 6P" }
                ],
                "dpi": [ 515.1, 518.0 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/Nexus 5X/*" },
                    { "ua": "Nexus 5X" }
                ],
                "dpi": [ 422.0, 419.9 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/LGMS345/*" },
                    { "ua": "LGMS345" }
                ],
                "dpi": [ 221.7, 219.1 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/LG-D800/*" },
                    { "ua": "LG-D800" }
                ],
                "dpi": [ 422.0, 424.1 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/LG-D850/*" },
                    { "ua": "LG-D850" }
                ],
                "dpi": [ 537.9, 541.9 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/VS985 4G/*" },
                    { "ua": "VS985 4G" }
                ],
                "dpi": [ 537.9, 535.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/Nexus 5/*" },
                    { "ua": "Nexus 5 " }
                ],
                "dpi": [ 442.4, 444.8 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/Nexus 4/*" },
                    { "ua": "Nexus 4" }
                ],
                "dpi": [ 319.8, 318.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/LG-P769/*" },
                    { "ua": "LG-P769" }
                ],
                "dpi": [ 240.6, 247.5 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/LGMS323/*" },
                    { "ua": "LGMS323" }
                ],
                "dpi": [ 206.6, 204.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "LGE/*/LGLS996/*" },
                    { "ua": "LGLS996" }
                ],
                "dpi": [ 403.4, 401.5 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Micromax/*/4560MMX/*" },
                    { "ua": "4560MMX" }
                ],
                "dpi": [ 240.0, 219.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Micromax/*/A250/*" },
                    { "ua": "Micromax A250" }
                ],
                "dpi": [ 480.0, 446.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Micromax/*/Micromax AQ4501/*" },
                    { "ua": "Micromax AQ4501" }
                ],
                "dpi": 240.0,
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/DROID RAZR/*" },
                    { "ua": "DROID RAZR" }
                ],
                "dpi": [ 368.1, 256.7 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT830C/*" },
                    { "ua": "XT830C" }
                ],
                "dpi": [ 254.0, 255.9 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1021/*" },
                    { "ua": "XT1021" }
                ],
                "dpi": [ 254.0, 256.7 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1023/*" },
                    { "ua": "XT1023" }
                ],
                "dpi": [ 254.0, 256.7 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1028/*" },
                    { "ua": "XT1028" }
                ],
                "dpi": [ 326.6, 327.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1034/*" },
                    { "ua": "XT1034" }
                ],
                "dpi": [ 326.6, 328.4 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1053/*" },
                    { "ua": "XT1053" }
                ],
                "dpi": [ 315.3, 316.1 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1562/*" },
                    { "ua": "XT1562" }
                ],
                "dpi": [ 403.4, 402.7 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/Nexus 6/*" },
                    { "ua": "Nexus 6 " }
                ],
                "dpi": [ 494.3, 489.7 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1063/*" },
                    { "ua": "XT1063" }
                ],
                "dpi": [ 295.0, 296.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1064/*" },
                    { "ua": "XT1064" }
                ],
                "dpi": [ 295.0, 295.6 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1092/*" },
                    { "ua": "XT1092" }
                ],
                "dpi": [ 422.0, 424.1 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "motorola/*/XT1095/*" },
                    { "ua": "XT1095" }
                ],
                "dpi": [ 422.0, 423.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "OnePlus/*/A0001/*" },
                    { "ua": "A0001" }
                ],
                "dpi": [ 403.4, 401.0 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "OnePlus/*/ONE E1005/*" },
                    { "ua": "ONE E1005" }
                ],
                "dpi": [ 442.4, 441.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "OnePlus/*/ONE A2005/*" },
                    { "ua": "ONE A2005" }
                ],
                "dpi": [ 391.9, 405.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "OPPO/*/X909/*" },
                    { "ua": "X909" }
                ],
                "dpi": [ 442.4, 444.1 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9082/*" },
                    { "ua": "GT-I9082" }
                ],
                "dpi": [ 184.7, 185.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G360P/*" },
                    { "ua": "SM-G360P" }
                ],
                "dpi": [ 196.7, 205.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/Nexus S/*" },
                    { "ua": "Nexus S" }
                ],
                "dpi": [ 234.5, 229.8 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9300/*" },
                    { "ua": "GT-I9300" }
                ],
                "dpi": [ 304.8, 303.9 ],
                "bw": 5,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-T230NU/*" },
                    { "ua": "SM-T230NU" }
                ],
                "dpi": 216.0,
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SGH-T399/*" },
                    { "ua": "SGH-T399" }
                ],
                "dpi": [ 217.7, 231.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-N9005/*" },
                    { "ua": "SM-N9005" }
                ],
                "dpi": [ 386.4, 387.0 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SAMSUNG-SM-N900A/*" },
                    { "ua": "SAMSUNG-SM-N900A" }
                ],
                "dpi": [ 386.4, 387.7 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9500/*" },
                    { "ua": "GT-I9500" }
                ],
                "dpi": [ 442.5, 443.3 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9505/*" },
                    { "ua": "GT-I9505" }
                ],
                "dpi": 439.4,
                "bw": 4,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G900F/*" },
                    { "ua": "SM-G900F" }
                ],
                "dpi": [ 415.6, 431.6 ],
                "bw": 5,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G900M/*" },
                    { "ua": "SM-G900M" }
                ],
                "dpi": [ 415.6, 431.6 ],
                "bw": 5,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G800F/*" },
                    { "ua": "SM-G800F" }
                ],
                "dpi": 326.8,
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G906S/*" },
                    { "ua": "SM-G906S" }
                ],
                "dpi": [ 562.7, 572.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9300/*" },
                    { "ua": "GT-I9300" }
                ],
                "dpi": [ 306.7, 304.8 ],
                "bw": 5,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-T535/*" },
                    { "ua": "SM-T535" }
                ],
                "dpi": [ 142.6, 136.4 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-N920C/*" },
                    { "ua": "SM-N920C" }
                ],
                "dpi": [ 515.1, 518.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9300I/*" },
                    { "ua": "GT-I9300I" }
                ],
                "dpi": [ 304.8, 305.8 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-I9195/*" },
                    { "ua": "GT-I9195" }
                ],
                "dpi": [ 249.4, 256.7 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SPH-L520/*" },
                    { "ua": "SPH-L520" }
                ],
                "dpi": [ 249.4, 255.9 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SAMSUNG-SGH-I717/*" },
                    { "ua": "SAMSUNG-SGH-I717" }
                ],
                "dpi": 285.8,
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SPH-D710/*" },
                    { "ua": "SPH-D710" }
                ],
                "dpi": [ 217.7, 204.2 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/GT-N7100/*" },
                    { "ua": "GT-N7100" }
                ],
                "dpi": 265.1,
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SCH-I605/*" },
                    { "ua": "SCH-I605" }
                ],
                "dpi": 265.1,
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/Galaxy Nexus/*" },
                    { "ua": "Galaxy Nexus" }
                ],
                "dpi": [ 315.3, 314.2 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-N910H/*" },
                    { "ua": "SM-N910H" }
                ],
                "dpi": [ 515.1, 518.0 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-N910C/*" },
                    { "ua": "SM-N910C" }
                ],
                "dpi": [ 515.2, 520.2 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G130M/*" },
                    { "ua": "SM-G130M" }
                ],
                "dpi": [ 165.9, 164.8 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G928I/*" },
                    { "ua": "SM-G928I" }
                ],
                "dpi": [ 515.1, 518.4 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G920F/*" },
                    { "ua": "SM-G920F" }
                ],
                "dpi": 580.6,
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G920P/*" },
                    { "ua": "SM-G920P" }
                ],
                "dpi": [ 522.5, 577.0 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G925F/*" },
                    { "ua": "SM-G925F" }
                ],
                "dpi": 580.6,
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "samsung/*/SM-G925V/*" },
                    { "ua": "SM-G925V" }
                ],
                "dpi": [ 522.5, 576.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Sony/*/C6903/*" },
                    { "ua": "C6903" }
                ],
                "dpi": [ 442.5, 443.3 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Sony/*/D6653/*" },
                    { "ua": "D6653" }
                ],
                "dpi": [ 428.6, 427.6 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Sony/*/E6653/*" },
                    { "ua": "E6653" }
                ],
                "dpi": [ 428.6, 425.7 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Sony/*/E6853/*" },
                    { "ua": "E6853" }
                ],
                "dpi": [ 403.4, 401.9 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "Sony/*/SGP321/*" },
                    { "ua": "SGP321" }
                ],
                "dpi": [ 224.7, 224.1 ],
                "bw": 3,
                "ac": 500
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "TCT/*/ALCATEL ONE TOUCH Fierce/*" },
                    { "ua": "ALCATEL ONE TOUCH Fierce" }
                ],
                "dpi": [ 240.0, 247.5 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "THL/*/thl 5000/*" },
                    { "ua": "thl 5000" }
                ],
                "dpi": [ 480.0, 443.3 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "android",
                "rules": [
                    { "mdmh": "ZTE/*/ZTE Blade L2/*" },
                    { "ua": "ZTE Blade L2" }
                ],
                "dpi": 240.0,
                "bw": 3,
                "ac": 500
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 640, 960 ] } ],
                "dpi": [ 325.1, 328.4 ],
                "bw": 4,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 640, 960 ] } ],
                "dpi": [ 325.1, 328.4 ],
                "bw": 4,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 640, 1136 ] } ],
                "dpi": [ 317.1, 320.2 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 640, 1136 ] } ],
                "dpi": [ 317.1, 320.2 ],
                "bw": 3,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 750, 1334 ] } ],
                "dpi": 326.4,
                "bw": 4,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 750, 1334 ] } ],
                "dpi": 326.4,
                "bw": 4,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 1242, 2208 ] } ],
                "dpi": [ 453.6, 458.4 ],
                "bw": 4,
                "ac": 1000
            },

            {
                "type": "ios",
                "rules": [ { "res": [ 1242, 2208 ] } ],
                "dpi": [ 453.6, 458.4 ],
                "bw": 4,
                "ac": 1000
            }
        ]};

    module.exports = DPDB_CACHE;

},{}],11:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

// Offline cache of the DPDB, to be used until we load the online one (and
// as a fallback in case we can't load the online one).
    var DPDB_CACHE = _dereq_('./dpdb-cache.js');
    var Util = _dereq_('../util.js');

// Online DPDB URL.
    var ONLINE_DPDB_URL = 'https://storage.googleapis.com/cardboard-dpdb/dpdb.json';

    /**
     * Calculates device parameters based on the DPDB (Device Parameter Database).
     * Initially, uses the cached DPDB values.
     *
     * If fetchOnline == true, then this object tries to fetch the online version
     * of the DPDB and updates the device info if a better match is found.
     * Calls the onDeviceParamsUpdated callback when there is an update to the
     * device information.
     */
    function Dpdb(fetchOnline, onDeviceParamsUpdated) {
        // Start with the offline DPDB cache while we are loading the real one.
        this.dpdb = DPDB_CACHE;

        // Calculate device params based on the offline version of the DPDB.
        this.recalculateDeviceParams_();

        // XHR to fetch online DPDB file, if requested.
        if (fetchOnline) {
            // Set the callback.
            this.onDeviceParamsUpdated = onDeviceParamsUpdated;

            console.log('Fetching DPDB...');
            var xhr = new XMLHttpRequest();
            var obj = this;
            xhr.open('GET', ONLINE_DPDB_URL, true);
            xhr.addEventListener('load', function() {
                obj.loading = false;
                if (xhr.status >= 200 && xhr.status <= 299) {
                    // Success.
                    console.log('Successfully loaded online DPDB.');
                    obj.dpdb = JSON.parse(xhr.response);
                    obj.recalculateDeviceParams_();
                } else {
                    // Error loading the DPDB.
                    console.error('Error loading online DPDB!');
                }
            });
            xhr.send();
        }
    }

// Returns the current device parameters.
    Dpdb.prototype.getDeviceParams = function() {
        return this.deviceParams;
    };

// Recalculates this device's parameters based on the DPDB.
    Dpdb.prototype.recalculateDeviceParams_ = function() {
        console.log('Recalculating device params.');
        var newDeviceParams = this.calcDeviceParams_();
        console.log('New device parameters:');
        console.log(newDeviceParams);
        if (newDeviceParams) {
            this.deviceParams = newDeviceParams;
            // Invoke callback, if it is set.
            if (this.onDeviceParamsUpdated) {
                this.onDeviceParamsUpdated(this.deviceParams);
            }
        } else {
            console.error('Failed to recalculate device parameters.');
        }
    };

// Returns a DeviceParams object that represents the best guess as to this
// device's parameters. Can return null if the device does not match any
// known devices.
    Dpdb.prototype.calcDeviceParams_ = function() {
        var db = this.dpdb; // shorthand
        if (!db) {
            console.error('DPDB not available.');
            return null;
        }
        if (db.format != 1) {
            console.error('DPDB has unexpected format version.');
            return null;
        }
        if (!db.devices || !db.devices.length) {
            console.error('DPDB does not have a devices section.');
            return null;
        }

        // Get the actual user agent and screen dimensions in pixels.
        var userAgent = navigator.userAgent || navigator.vendor || window.opera;
        var width = Util.getScreenWidth();
        var height = Util.getScreenHeight();
        console.log('User agent: ' + userAgent);
        console.log('Pixel width: ' + width);
        console.log('Pixel height: ' + height);

        if (!db.devices) {
            console.error('DPDB has no devices section.');
            return null;
        }

        for (var i = 0; i < db.devices.length; i++) {
            var device = db.devices[i];
            if (!device.rules) {
                console.warn('Device[' + i + '] has no rules section.');
                continue;
            }

            if (device.type != 'ios' && device.type != 'android') {
                console.warn('Device[' + i + '] has invalid type.');
                continue;
            }

            // See if this device is of the appropriate type.
            if (Util.isIOS() != (device.type == 'ios')) continue;

            // See if this device matches any of the rules:
            var matched = false;
            for (var j = 0; j < device.rules.length; j++) {
                var rule = device.rules[j];
                if (this.matchRule_(rule, userAgent, width, height)) {
                    console.log('Rule matched:');
                    console.log(rule);
                    matched = true;
                    break;
                }
            }
            if (!matched) continue;

            // device.dpi might be an array of [ xdpi, ydpi] or just a scalar.
            var xdpi = device.dpi[0] || device.dpi;
            var ydpi = device.dpi[1] || device.dpi;

            return new DeviceParams({ xdpi: xdpi, ydpi: ydpi, bevelMm: device.bw });
        }

        console.warn('No DPDB device match.');
        return null;
    };

    Dpdb.prototype.matchRule_ = function(rule, ua, screenWidth, screenHeight) {
        // We can only match 'ua' and 'res' rules, not other types like 'mdmh'
        // (which are meant for native platforms).
        if (!rule.ua && !rule.res) return false;

        // If our user agent string doesn't contain the indicated user agent string,
        // the match fails.
        if (rule.ua && ua.indexOf(rule.ua) < 0) return false;

        // If the rule specifies screen dimensions that don't correspond to ours,
        // the match fails.
        if (rule.res) {
            if (!rule.res[0] || !rule.res[1]) return false;
            var resX = rule.res[0];
            var resY = rule.res[1];
            // Compare min and max so as to make the order not matter, i.e., it should
            // be true that 640x480 == 480x640.
            if (Math.min(screenWidth, screenHeight) != Math.min(resX, resY) ||
                (Math.max(screenWidth, screenHeight) != Math.max(resX, resY))) {
                return false;
            }
        }

        return true;
    }

    function DeviceParams(params) {
        this.xdpi = params.xdpi;
        this.ydpi = params.ydpi;
        this.bevelMm = params.bevelMm;
    }

    module.exports = Dpdb;
},{"../util.js":22,"./dpdb-cache.js":10}],12:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    function Emitter() {
        this.callbacks = {};
    }

    Emitter.prototype.emit = function(eventName) {
        var callbacks = this.callbacks[eventName];
        if (!callbacks) {
            //console.log('No valid callback specified.');
            return;
        }
        var args = [].slice.call(arguments);
        // Eliminate the first param (the callback).
        args.shift();
        for (var i = 0; i < callbacks.length; i++) {
            callbacks[i].apply(this, args);
        }
    };

    Emitter.prototype.on = function(eventName, callback) {
        if (eventName in this.callbacks) {
            this.callbacks[eventName].push(callback);
        } else {
            this.callbacks[eventName] = [callback];
        }
    };

    module.exports = Emitter;

},{}],13:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    var WebVRPolyfill = _dereq_('./webvr-polyfill.js');

// Initialize a WebVRConfig just in case.
    window.WebVRConfig = window.WebVRConfig || {};

    if (!window.WebVRConfig.DEFER_INITIALIZATION) {
        new WebVRPolyfill();
    } else {
        window.InitializeWebVRPolyfill = function() {
            new WebVRPolyfill();
        }
    }

},{"./webvr-polyfill.js":25}],14:[function(_dereq_,module,exports){
    /*
     * Copyright 2016 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var VRDisplay = _dereq_('./base.js').VRDisplay;
    var THREE = _dereq_('./three-math.js');
    var Util = _dereq_('./util.js');

// How much to rotate per key stroke.
    var KEY_SPEED = 0.15;
    var KEY_ANIMATION_DURATION = 80;

// How much to rotate for mouse events.
    var MOUSE_SPEED_X = 0.5;
    var MOUSE_SPEED_Y = 0.3;

    /**
     * VRDisplay based on mouse and keyboard input. Designed for desktops/laptops
     * where orientation events aren't supported. Cannot present.
     */
    function MouseKeyboardVRDisplay() {
        this.displayName = 'Mouse and Keyboard VRDisplay (webvr-polyfill)';

        this.capabilities.hasOrientation = true;

        // Attach to mouse and keyboard events.
        window.addEventListener('keydown', this.onKeyDown_.bind(this));
        window.addEventListener('mousemove', this.onMouseMove_.bind(this));
        window.addEventListener('mousedown', this.onMouseDown_.bind(this));
        window.addEventListener('mouseup', this.onMouseUp_.bind(this));

        // "Private" members.
        this.phi_ = 0;
        this.theta_ = 0;

        // Variables for keyboard-based rotation animation.
        this.targetAngle_ = null;
        this.angleAnimation_ = null;

        // State variables for calculations.
        this.euler_ = new THREE.Euler();
        this.orientation_ = new THREE.Quaternion();

        // Variables for mouse-based rotation.
        this.rotateStart_ = new THREE.Vector2();
        this.rotateEnd_ = new THREE.Vector2();
        this.rotateDelta_ = new THREE.Vector2();
        this.isDragging_ = false;

        this.orientationOut_ = new Float32Array(4);
    }
    MouseKeyboardVRDisplay.prototype = new VRDisplay();

    MouseKeyboardVRDisplay.prototype.getImmediatePose = function() {
        this.euler_.set(this.phi_, this.theta_, 0, 'YXZ');
        this.orientation_.setFromEuler(this.euler_);

        this.orientationOut_[0] = this.orientation_.x;
        this.orientationOut_[1] = this.orientation_.y;
        this.orientationOut_[2] = this.orientation_.z;
        this.orientationOut_[3] = this.orientation_.w;

        return {
            position: null,
            orientation: this.orientationOut_,
            linearVelocity: null,
            linearAcceleration: null,
            angularVelocity: null,
            angularAcceleration: null
        };
    };

    MouseKeyboardVRDisplay.prototype.onKeyDown_ = function(e) {
        // Track WASD and arrow keys.
        if (e.keyCode == 38) { // Up key.
            this.animatePhi_(this.phi_ + KEY_SPEED);
        } else if (e.keyCode == 39) { // Right key.
            this.animateTheta_(this.theta_ - KEY_SPEED);
        } else if (e.keyCode == 40) { // Down key.
            this.animatePhi_(this.phi_ - KEY_SPEED);
        } else if (e.keyCode == 37) { // Left key.
            this.animateTheta_(this.theta_ + KEY_SPEED);
        }
    };

    MouseKeyboardVRDisplay.prototype.animateTheta_ = function(targetAngle) {
        this.animateKeyTransitions_('theta_', targetAngle);
    };

    MouseKeyboardVRDisplay.prototype.animatePhi_ = function(targetAngle) {
        // Prevent looking too far up or down.
        targetAngle = Util.clamp(targetAngle, -Math.PI/2, Math.PI/2);
        this.animateKeyTransitions_('phi_', targetAngle);
    };

    /**
     * Start an animation to transition an angle from one value to another.
     */
    MouseKeyboardVRDisplay.prototype.animateKeyTransitions_ = function(angleName, targetAngle) {
        // If an animation is currently running, cancel it.
        if (this.angleAnimation_) {
            clearInterval(this.angleAnimation_);
        }
        var startAngle = this[angleName];
        var startTime = new Date();
        // Set up an interval timer to perform the animation.
        this.angleAnimation_ = setInterval(function() {
            // Once we're finished the animation, we're done.
            var elapsed = new Date() - startTime;
            if (elapsed >= KEY_ANIMATION_DURATION) {
                this[angleName] = targetAngle;
                clearInterval(this.angleAnimation_);
                return;
            }
            // Linearly interpolate the angle some amount.
            var percent = elapsed / KEY_ANIMATION_DURATION;
            this[angleName] = startAngle + (targetAngle - startAngle) * percent;
        }.bind(this), 1000/60);
    };

    MouseKeyboardVRDisplay.prototype.onMouseDown_ = function(e) {
        this.rotateStart_.set(e.clientX, e.clientY);
        this.isDragging_ = true;
    };

// Very similar to https://gist.github.com/mrflix/8351020
    MouseKeyboardVRDisplay.prototype.onMouseMove_ = function(e) {
        if (!this.isDragging_ && !this.isPointerLocked_()) {
            return;
        }
        // Support pointer lock API.
        if (this.isPointerLocked_()) {
            var movementX = e.movementX || e.mozMovementX || 0;
            var movementY = e.movementY || e.mozMovementY || 0;
            this.rotateEnd_.set(this.rotateStart_.x - movementX, this.rotateStart_.y - movementY);
        } else {
            this.rotateEnd_.set(e.clientX, e.clientY);
        }
        // Calculate how much we moved in mouse space.
        this.rotateDelta_.subVectors(this.rotateEnd_, this.rotateStart_);
        this.rotateStart_.copy(this.rotateEnd_);

        // Keep track of the cumulative euler angles.
        this.phi_ += 2 * Math.PI * this.rotateDelta_.y / screen.height * MOUSE_SPEED_Y;
        this.theta_ += 2 * Math.PI * this.rotateDelta_.x / screen.width * MOUSE_SPEED_X;

        // Prevent looking too far up or down.
        this.phi_ = Util.clamp(this.phi_, -Math.PI/2, Math.PI/2);
    };

    MouseKeyboardVRDisplay.prototype.onMouseUp_ = function(e) {
        this.isDragging_ = false;
    };

    MouseKeyboardVRDisplay.prototype.isPointerLocked_ = function() {
        var el = document.pointerLockElement || document.mozPointerLockElement ||
            document.webkitPointerLockElement;
        return el !== undefined;
    };

    MouseKeyboardVRDisplay.prototype.resetPose = function() {
        this.phi_ = 0;
        this.theta_ = 0;
    };

    module.exports = MouseKeyboardVRDisplay;

},{"./base.js":2,"./three-math.js":20,"./util.js":22}],15:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var Util = _dereq_('./util.js');

    function RotateInstructions() {
        this.loadIcon_();

        var overlay = document.createElement('div');
        var s = overlay.style;
        s.position = 'fixed';
        s.top = 0;
        s.right = 0;
        s.bottom = 0;
        s.left = 0;
        s.backgroundColor = 'gray';
        s.fontFamily = 'sans-serif';

        var img = document.createElement('img');
        img.src = this.icon;
        var s = img.style;
        s.marginLeft = '25%';
        s.marginTop = '25%';
        s.width = '50%';
        overlay.appendChild(img);

        var text = document.createElement('div');
        var s = text.style;
        s.textAlign = 'center';
        s.fontSize = '16px';
        s.lineHeight = '24px';
        s.margin = '24px 25%';
        s.width = '50%';
        text.innerHTML = 'Place your phone into your Cardboard viewer.';
        overlay.appendChild(text);

        var snackbar = document.createElement('div');
        var s = snackbar.style;
        s.backgroundColor = '#CFD8DC';
        s.position = 'fixed';
        s.bottom = 0;
        s.width = '100%';
        s.height = '48px';
        s.padding = '14px 24px';
        s.boxSizing = 'border-box';
        s.color = '#656A6B';
        overlay.appendChild(snackbar);

        var snackbarText = document.createElement('div');
        snackbarText.style.float = 'left';
        snackbarText.innerHTML = 'No Cardboard viewer?';

        var snackbarButton = document.createElement('a');
        snackbarButton.href = 'https://www.google.com/get/cardboard/get-cardboard/';
        snackbarButton.innerHTML = 'get one';
        var s = snackbarButton.style;
        s.float = 'right';
        s.fontWeight = 600;
        s.textTransform = 'uppercase';
        s.borderLeft = '1px solid gray';
        s.paddingLeft = '24px';
        s.textDecoration = 'none';
        s.color = '#656A6B';

        snackbar.appendChild(snackbarText);
        snackbar.appendChild(snackbarButton);

        this.overlay = overlay;
        this.text = text;

        this.hide();
    }

    RotateInstructions.prototype.show = function(parent) {
        if (!parent && !this.overlay.parentElement) {
            document.body.appendChild(this.overlay);
        } else if (parent) {
            if (this.overlay.parentElement && this.overlay.parentElement != parent)
                this.overlay.parentElement.removeChild(this.overlay);

            parent.appendChild(this.overlay);
        }

        this.overlay.style.display = 'block';

        var img = this.overlay.querySelector('img');
        var s = img.style;

        if (Util.isLandscapeMode()) {
            s.width = '20%';
            s.marginLeft = '40%';
            s.marginTop = '3%';
        } else {
            s.width = '50%';
            s.marginLeft = '25%';
            s.marginTop = '25%';
        }
    };

    RotateInstructions.prototype.hide = function() {
        this.overlay.style.display = 'none';
    };

    RotateInstructions.prototype.showTemporarily = function(ms, parent) {
        this.show(parent);
        this.timer = setTimeout(this.hide.bind(this), ms);
    };

    RotateInstructions.prototype.disableShowTemporarily = function() {
        clearTimeout(this.timer);
    };

    RotateInstructions.prototype.update = function() {
        this.disableShowTemporarily();
        // In portrait VR mode, tell the user to rotate to landscape. Otherwise, hide
        // the instructions.
        if (!Util.isLandscapeMode() && Util.isMobile()) {
            this.show();
        } else {
            this.hide();
        }
    };

    RotateInstructions.prototype.loadIcon_ = function() {
        // Encoded asset_src/rotate-instructions.svg
        this.icon = Util.base64('image/svg+xml', 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE5OHB4IiBoZWlnaHQ9IjI0MHB4IiB2aWV3Qm94PSIwIDAgMTk4IDI0MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDMuMy4zICgxMjA4MSkgLSBodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2ggLS0+CiAgICA8dGl0bGU+dHJhbnNpdGlvbjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPgogICAgICAgIDxnIGlkPSJ0cmFuc2l0aW9uIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIj4KICAgICAgICAgICAgPGcgaWQ9IkltcG9ydGVkLUxheWVycy1Db3B5LTQtKy1JbXBvcnRlZC1MYXllcnMtQ29weS0rLUltcG9ydGVkLUxheWVycy1Db3B5LTItQ29weSIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iSW1wb3J0ZWQtTGF5ZXJzLUNvcHktNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsIDEwNy4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQ5LjYyNSwyLjUyNyBDMTQ5LjYyNSwyLjUyNyAxNTUuODA1LDYuMDk2IDE1Ni4zNjIsNi40MTggTDE1Ni4zNjIsNy4zMDQgQzE1Ni4zNjIsNy40ODEgMTU2LjM3NSw3LjY2NCAxNTYuNCw3Ljg1MyBDMTU2LjQxLDcuOTM0IDE1Ni40Miw4LjAxNSAxNTYuNDI3LDguMDk1IEMxNTYuNTY3LDkuNTEgMTU3LjQwMSwxMS4wOTMgMTU4LjUzMiwxMi4wOTQgTDE2NC4yNTIsMTcuMTU2IEwxNjQuMzMzLDE3LjA2NiBDMTY0LjMzMywxNy4wNjYgMTY4LjcxNSwxNC41MzYgMTY5LjU2OCwxNC4wNDIgQzE3MS4wMjUsMTQuODgzIDE5NS41MzgsMjkuMDM1IDE5NS41MzgsMjkuMDM1IEwxOTUuNTM4LDgzLjAzNiBDMTk1LjUzOCw4My44MDcgMTk1LjE1Miw4NC4yNTMgMTk0LjU5LDg0LjI1MyBDMTk0LjM1Nyw4NC4yNTMgMTk0LjA5NSw4NC4xNzcgMTkzLjgxOCw4NC4wMTcgTDE2OS44NTEsNzAuMTc5IEwxNjkuODM3LDcwLjIwMyBMMTQyLjUxNSw4NS45NzggTDE0MS42NjUsODQuNjU1IEMxMzYuOTM0LDgzLjEyNiAxMzEuOTE3LDgxLjkxNSAxMjYuNzE0LDgxLjA0NSBDMTI2LjcwOSw4MS4wNiAxMjYuNzA3LDgxLjA2OSAxMjYuNzA3LDgxLjA2OSBMMTIxLjY0LDk4LjAzIEwxMTMuNzQ5LDEwMi41ODYgTDExMy43MTIsMTAyLjUyMyBMMTEzLjcxMiwxMzAuMTEzIEMxMTMuNzEyLDEzMC44ODUgMTEzLjMyNiwxMzEuMzMgMTEyLjc2NCwxMzEuMzMgQzExMi41MzIsMTMxLjMzIDExMi4yNjksMTMxLjI1NCAxMTEuOTkyLDEzMS4wOTQgTDY5LjUxOSwxMDYuNTcyIEM2OC41NjksMTA2LjAyMyA2Ny43OTksMTA0LjY5NSA2Ny43OTksMTAzLjYwNSBMNjcuNzk5LDEwMi41NyBMNjcuNzc4LDEwMi42MTcgQzY3LjI3LDEwMi4zOTMgNjYuNjQ4LDEwMi4yNDkgNjUuOTYyLDEwMi4yMTggQzY1Ljg3NSwxMDIuMjE0IDY1Ljc4OCwxMDIuMjEyIDY1LjcwMSwxMDIuMjEyIEM2NS42MDYsMTAyLjIxMiA2NS41MTEsMTAyLjIxNSA2NS40MTYsMTAyLjIxOSBDNjUuMTk1LDEwMi4yMjkgNjQuOTc0LDEwMi4yMzUgNjQuNzU0LDEwMi4yMzUgQzY0LjMzMSwxMDIuMjM1IDYzLjkxMSwxMDIuMjE2IDYzLjQ5OCwxMDIuMTc4IEM2MS44NDMsMTAyLjAyNSA2MC4yOTgsMTAxLjU3OCA1OS4wOTQsMTAwLjg4MiBMMTIuNTE4LDczLjk5MiBMMTIuNTIzLDc0LjAwNCBMMi4yNDUsNTUuMjU0IEMxLjI0NCw1My40MjcgMi4wMDQsNTEuMDM4IDMuOTQzLDQ5LjkxOCBMNTkuOTU0LDE3LjU3MyBDNjAuNjI2LDE3LjE4NSA2MS4zNSwxNy4wMDEgNjIuMDUzLDE3LjAwMSBDNjMuMzc5LDE3LjAwMSA2NC42MjUsMTcuNjYgNjUuMjgsMTguODU0IEw2NS4yODUsMTguODUxIEw2NS41MTIsMTkuMjY0IEw2NS41MDYsMTkuMjY4IEM2NS45MDksMjAuMDAzIDY2LjQwNSwyMC42OCA2Ni45ODMsMjEuMjg2IEw2Ny4yNiwyMS41NTYgQzY5LjE3NCwyMy40MDYgNzEuNzI4LDI0LjM1NyA3NC4zNzMsMjQuMzU3IEM3Ni4zMjIsMjQuMzU3IDc4LjMyMSwyMy44NCA4MC4xNDgsMjIuNzg1IEM4MC4xNjEsMjIuNzg1IDg3LjQ2NywxOC41NjYgODcuNDY3LDE4LjU2NiBDODguMTM5LDE4LjE3OCA4OC44NjMsMTcuOTk0IDg5LjU2NiwxNy45OTQgQzkwLjg5MiwxNy45OTQgOTIuMTM4LDE4LjY1MiA5Mi43OTIsMTkuODQ3IEw5Ni4wNDIsMjUuNzc1IEw5Ni4wNjQsMjUuNzU3IEwxMDIuODQ5LDI5LjY3NCBMMTAyLjc0NCwyOS40OTIgTDE0OS42MjUsMi41MjcgTTE0OS42MjUsMC44OTIgQzE0OS4zNDMsMC44OTIgMTQ5LjA2MiwwLjk2NSAxNDguODEsMS4xMSBMMTAyLjY0MSwyNy42NjYgTDk3LjIzMSwyNC41NDIgTDk0LjIyNiwxOS4wNjEgQzkzLjMxMywxNy4zOTQgOTEuNTI3LDE2LjM1OSA4OS41NjYsMTYuMzU4IEM4OC41NTUsMTYuMzU4IDg3LjU0NiwxNi42MzIgODYuNjQ5LDE3LjE1IEM4My44NzgsMTguNzUgNzkuNjg3LDIxLjE2OSA3OS4zNzQsMjEuMzQ1IEM3OS4zNTksMjEuMzUzIDc5LjM0NSwyMS4zNjEgNzkuMzMsMjEuMzY5IEM3Ny43OTgsMjIuMjU0IDc2LjA4NCwyMi43MjIgNzQuMzczLDIyLjcyMiBDNzIuMDgxLDIyLjcyMiA2OS45NTksMjEuODkgNjguMzk3LDIwLjM4IEw2OC4xNDUsMjAuMTM1IEM2Ny43MDYsMTkuNjcyIDY3LjMyMywxOS4xNTYgNjcuMDA2LDE4LjYwMSBDNjYuOTg4LDE4LjU1OSA2Ni45NjgsMTguNTE5IDY2Ljk0NiwxOC40NzkgTDY2LjcxOSwxOC4wNjUgQzY2LjY5LDE4LjAxMiA2Ni42NTgsMTcuOTYgNjYuNjI0LDE3LjkxMSBDNjUuNjg2LDE2LjMzNyA2My45NTEsMTUuMzY2IDYyLjA1MywxNS4zNjYgQzYxLjA0MiwxNS4zNjYgNjAuMDMzLDE1LjY0IDU5LjEzNiwxNi4xNTggTDMuMTI1LDQ4LjUwMiBDMC40MjYsNTAuMDYxIC0wLjYxMyw1My40NDIgMC44MTEsNTYuMDQgTDExLjA4OSw3NC43OSBDMTEuMjY2LDc1LjExMyAxMS41MzcsNzUuMzUzIDExLjg1LDc1LjQ5NCBMNTguMjc2LDEwMi4yOTggQzU5LjY3OSwxMDMuMTA4IDYxLjQzMywxMDMuNjMgNjMuMzQ4LDEwMy44MDYgQzYzLjgxMiwxMDMuODQ4IDY0LjI4NSwxMDMuODcgNjQuNzU0LDEwMy44NyBDNjUsMTAzLjg3IDY1LjI0OSwxMDMuODY0IDY1LjQ5NCwxMDMuODUyIEM2NS41NjMsMTAzLjg0OSA2NS42MzIsMTAzLjg0NyA2NS43MDEsMTAzLjg0NyBDNjUuNzY0LDEwMy44NDcgNjUuODI4LDEwMy44NDkgNjUuODksMTAzLjg1MiBDNjUuOTg2LDEwMy44NTYgNjYuMDgsMTAzLjg2MyA2Ni4xNzMsMTAzLjg3NCBDNjYuMjgyLDEwNS40NjcgNjcuMzMyLDEwNy4xOTcgNjguNzAyLDEwNy45ODggTDExMS4xNzQsMTMyLjUxIEMxMTEuNjk4LDEzMi44MTIgMTEyLjIzMiwxMzIuOTY1IDExMi43NjQsMTMyLjk2NSBDMTE0LjI2MSwxMzIuOTY1IDExNS4zNDcsMTMxLjc2NSAxMTUuMzQ3LDEzMC4xMTMgTDExNS4zNDcsMTAzLjU1MSBMMTIyLjQ1OCw5OS40NDYgQzEyMi44MTksOTkuMjM3IDEyMy4wODcsOTguODk4IDEyMy4yMDcsOTguNDk4IEwxMjcuODY1LDgyLjkwNSBDMTMyLjI3OSw4My43MDIgMTM2LjU1Nyw4NC43NTMgMTQwLjYwNyw4Ni4wMzMgTDE0MS4xNCw4Ni44NjIgQzE0MS40NTEsODcuMzQ2IDE0MS45NzcsODcuNjEzIDE0Mi41MTYsODcuNjEzIEMxNDIuNzk0LDg3LjYxMyAxNDMuMDc2LDg3LjU0MiAxNDMuMzMzLDg3LjM5MyBMMTY5Ljg2NSw3Mi4wNzYgTDE5Myw4NS40MzMgQzE5My41MjMsODUuNzM1IDE5NC4wNTgsODUuODg4IDE5NC41OSw4NS44ODggQzE5Ni4wODcsODUuODg4IDE5Ny4xNzMsODQuNjg5IDE5Ny4xNzMsODMuMDM2IEwxOTcuMTczLDI5LjAzNSBDMTk3LjE3MywyOC40NTEgMTk2Ljg2MSwyNy45MTEgMTk2LjM1NSwyNy42MTkgQzE5Ni4zNTUsMjcuNjE5IDE3MS44NDMsMTMuNDY3IDE3MC4zODUsMTIuNjI2IEMxNzAuMTMyLDEyLjQ4IDE2OS44NSwxMi40MDcgMTY5LjU2OCwxMi40MDcgQzE2OS4yODUsMTIuNDA3IDE2OS4wMDIsMTIuNDgxIDE2OC43NDksMTIuNjI3IEMxNjguMTQzLDEyLjk3OCAxNjUuNzU2LDE0LjM1NyAxNjQuNDI0LDE1LjEyNSBMMTU5LjYxNSwxMC44NyBDMTU4Ljc5NiwxMC4xNDUgMTU4LjE1NCw4LjkzNyAxNTguMDU0LDcuOTM0IEMxNTguMDQ1LDcuODM3IDE1OC4wMzQsNy43MzkgMTU4LjAyMSw3LjY0IEMxNTguMDA1LDcuNTIzIDE1Ny45OTgsNy40MSAxNTcuOTk4LDcuMzA0IEwxNTcuOTk4LDYuNDE4IEMxNTcuOTk4LDUuODM0IDE1Ny42ODYsNS4yOTUgMTU3LjE4MSw1LjAwMiBDMTU2LjYyNCw0LjY4IDE1MC40NDIsMS4xMTEgMTUwLjQ0MiwxLjExMSBDMTUwLjE4OSwwLjk2NSAxNDkuOTA3LDAuODkyIDE0OS42MjUsMC44OTIiIGlkPSJGaWxsLTEiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTYuMDI3LDI1LjYzNiBMMTQyLjYwMyw1Mi41MjcgQzE0My44MDcsNTMuMjIyIDE0NC41ODIsNTQuMTE0IDE0NC44NDUsNTUuMDY4IEwxNDQuODM1LDU1LjA3NSBMNjMuNDYxLDEwMi4wNTcgTDYzLjQ2LDEwMi4wNTcgQzYxLjgwNiwxMDEuOTA1IDYwLjI2MSwxMDEuNDU3IDU5LjA1NywxMDAuNzYyIEwxMi40ODEsNzMuODcxIEw5Ni4wMjcsMjUuNjM2IiBpZD0iRmlsbC0yIiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTYzLjQ2MSwxMDIuMTc0IEM2My40NTMsMTAyLjE3NCA2My40NDYsMTAyLjE3NCA2My40MzksMTAyLjE3MiBDNjEuNzQ2LDEwMi4wMTYgNjAuMjExLDEwMS41NjMgNTguOTk4LDEwMC44NjMgTDEyLjQyMiw3My45NzMgQzEyLjM4Niw3My45NTIgMTIuMzY0LDczLjkxNCAxMi4zNjQsNzMuODcxIEMxMi4zNjQsNzMuODMgMTIuMzg2LDczLjc5MSAxMi40MjIsNzMuNzcgTDk1Ljk2OCwyNS41MzUgQzk2LjAwNCwyNS41MTQgOTYuMDQ5LDI1LjUxNCA5Ni4wODUsMjUuNTM1IEwxNDIuNjYxLDUyLjQyNiBDMTQzLjg4OCw1My4xMzQgMTQ0LjY4Miw1NC4wMzggMTQ0Ljk1Nyw1NS4wMzcgQzE0NC45Nyw1NS4wODMgMTQ0Ljk1Myw1NS4xMzMgMTQ0LjkxNSw1NS4xNjEgQzE0NC45MTEsNTUuMTY1IDE0NC44OTgsNTUuMTc0IDE0NC44OTQsNTUuMTc3IEw2My41MTksMTAyLjE1OCBDNjMuNTAxLDEwMi4xNjkgNjMuNDgxLDEwMi4xNzQgNjMuNDYxLDEwMi4xNzQgTDYzLjQ2MSwxMDIuMTc0IFogTTEyLjcxNCw3My44NzEgTDU5LjExNSwxMDAuNjYxIEM2MC4yOTMsMTAxLjM0MSA2MS43ODYsMTAxLjc4MiA2My40MzUsMTAxLjkzNyBMMTQ0LjcwNyw1NS4wMTUgQzE0NC40MjgsNTQuMTA4IDE0My42ODIsNTMuMjg1IDE0Mi41NDQsNTIuNjI4IEw5Ni4wMjcsMjUuNzcxIEwxMi43MTQsNzMuODcxIEwxMi43MTQsNzMuODcxIFoiIGlkPSJGaWxsLTMiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQ4LjMyNyw1OC40NzEgQzE0OC4xNDUsNTguNDggMTQ3Ljk2Miw1OC40OCAxNDcuNzgxLDU4LjQ3MiBDMTQ1Ljg4Nyw1OC4zODkgMTQ0LjQ3OSw1Ny40MzQgMTQ0LjYzNiw1Ni4zNCBDMTQ0LjY4OSw1NS45NjcgMTQ0LjY2NCw1NS41OTcgMTQ0LjU2NCw1NS4yMzUgTDYzLjQ2MSwxMDIuMDU3IEM2NC4wODksMTAyLjExNSA2NC43MzMsMTAyLjEzIDY1LjM3OSwxMDIuMDk5IEM2NS41NjEsMTAyLjA5IDY1Ljc0MywxMDIuMDkgNjUuOTI1LDEwMi4wOTggQzY3LjgxOSwxMDIuMTgxIDY5LjIyNywxMDMuMTM2IDY5LjA3LDEwNC4yMyBMMTQ4LjMyNyw1OC40NzEiIGlkPSJGaWxsLTQiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNjkuMDcsMTA0LjM0NyBDNjkuMDQ4LDEwNC4zNDcgNjkuMDI1LDEwNC4zNCA2OS4wMDUsMTA0LjMyNyBDNjguOTY4LDEwNC4zMDEgNjguOTQ4LDEwNC4yNTcgNjguOTU1LDEwNC4yMTMgQzY5LDEwMy44OTYgNjguODk4LDEwMy41NzYgNjguNjU4LDEwMy4yODggQzY4LjE1MywxMDIuNjc4IDY3LjEwMywxMDIuMjY2IDY1LjkyLDEwMi4yMTQgQzY1Ljc0MiwxMDIuMjA2IDY1LjU2MywxMDIuMjA3IDY1LjM4NSwxMDIuMjE1IEM2NC43NDIsMTAyLjI0NiA2NC4wODcsMTAyLjIzMiA2My40NSwxMDIuMTc0IEM2My4zOTksMTAyLjE2OSA2My4zNTgsMTAyLjEzMiA2My4zNDcsMTAyLjA4MiBDNjMuMzM2LDEwMi4wMzMgNjMuMzU4LDEwMS45ODEgNjMuNDAyLDEwMS45NTYgTDE0NC41MDYsNTUuMTM0IEMxNDQuNTM3LDU1LjExNiAxNDQuNTc1LDU1LjExMyAxNDQuNjA5LDU1LjEyNyBDMTQ0LjY0Miw1NS4xNDEgMTQ0LjY2OCw1NS4xNyAxNDQuNjc3LDU1LjIwNCBDMTQ0Ljc4MSw1NS41ODUgMTQ0LjgwNiw1NS45NzIgMTQ0Ljc1MSw1Ni4zNTcgQzE0NC43MDYsNTYuNjczIDE0NC44MDgsNTYuOTk0IDE0NS4wNDcsNTcuMjgyIEMxNDUuNTUzLDU3Ljg5MiAxNDYuNjAyLDU4LjMwMyAxNDcuNzg2LDU4LjM1NSBDMTQ3Ljk2NCw1OC4zNjMgMTQ4LjE0Myw1OC4zNjMgMTQ4LjMyMSw1OC4zNTQgQzE0OC4zNzcsNTguMzUyIDE0OC40MjQsNTguMzg3IDE0OC40MzksNTguNDM4IEMxNDguNDU0LDU4LjQ5IDE0OC40MzIsNTguNTQ1IDE0OC4zODUsNTguNTcyIEw2OS4xMjksMTA0LjMzMSBDNjkuMTExLDEwNC4zNDIgNjkuMDksMTA0LjM0NyA2OS4wNywxMDQuMzQ3IEw2OS4wNywxMDQuMzQ3IFogTTY1LjY2NSwxMDEuOTc1IEM2NS43NTQsMTAxLjk3NSA2NS44NDIsMTAxLjk3NyA2NS45MywxMDEuOTgxIEM2Ny4xOTYsMTAyLjAzNyA2OC4yODMsMTAyLjQ2OSA2OC44MzgsMTAzLjEzOSBDNjkuMDY1LDEwMy40MTMgNjkuMTg4LDEwMy43MTQgNjkuMTk4LDEwNC4wMjEgTDE0Ny44ODMsNTguNTkyIEMxNDcuODQ3LDU4LjU5MiAxNDcuODExLDU4LjU5MSAxNDcuNzc2LDU4LjU4OSBDMTQ2LjUwOSw1OC41MzMgMTQ1LjQyMiw1OC4xIDE0NC44NjcsNTcuNDMxIEMxNDQuNTg1LDU3LjA5MSAxNDQuNDY1LDU2LjcwNyAxNDQuNTIsNTYuMzI0IEMxNDQuNTYzLDU2LjAyMSAxNDQuNTUyLDU1LjcxNiAxNDQuNDg4LDU1LjQxNCBMNjMuODQ2LDEwMS45NyBDNjQuMzUzLDEwMi4wMDIgNjQuODY3LDEwMi4wMDYgNjUuMzc0LDEwMS45ODIgQzY1LjQ3MSwxMDEuOTc3IDY1LjU2OCwxMDEuOTc1IDY1LjY2NSwxMDEuOTc1IEw2NS42NjUsMTAxLjk3NSBaIiBpZD0iRmlsbC01IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTIuMjA4LDU1LjEzNCBDMS4yMDcsNTMuMzA3IDEuOTY3LDUwLjkxNyAzLjkwNiw0OS43OTcgTDU5LjkxNywxNy40NTMgQzYxLjg1NiwxNi4zMzMgNjQuMjQxLDE2LjkwNyA2NS4yNDMsMTguNzM0IEw2NS40NzUsMTkuMTQ0IEM2NS44NzIsMTkuODgyIDY2LjM2OCwyMC41NiA2Ni45NDUsMjEuMTY1IEw2Ny4yMjMsMjEuNDM1IEM3MC41NDgsMjQuNjQ5IDc1LjgwNiwyNS4xNTEgODAuMTExLDIyLjY2NSBMODcuNDMsMTguNDQ1IEM4OS4zNywxNy4zMjYgOTEuNzU0LDE3Ljg5OSA5Mi43NTUsMTkuNzI3IEw5Ni4wMDUsMjUuNjU1IEwxMi40ODYsNzMuODg0IEwyLjIwOCw1NS4xMzQgWiIgaWQ9IkZpbGwtNiIgZmlsbD0iI0ZBRkFGQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMi40ODYsNzQuMDAxIEMxMi40NzYsNzQuMDAxIDEyLjQ2NSw3My45OTkgMTIuNDU1LDczLjk5NiBDMTIuNDI0LDczLjk4OCAxMi4zOTksNzMuOTY3IDEyLjM4NCw3My45NCBMMi4xMDYsNTUuMTkgQzEuMDc1LDUzLjMxIDEuODU3LDUwLjg0NSAzLjg0OCw0OS42OTYgTDU5Ljg1OCwxNy4zNTIgQzYwLjUyNSwxNi45NjcgNjEuMjcxLDE2Ljc2NCA2Mi4wMTYsMTYuNzY0IEM2My40MzEsMTYuNzY0IDY0LjY2NiwxNy40NjYgNjUuMzI3LDE4LjY0NiBDNjUuMzM3LDE4LjY1NCA2NS4zNDUsMTguNjYzIDY1LjM1MSwxOC42NzQgTDY1LjU3OCwxOS4wODggQzY1LjU4NCwxOS4xIDY1LjU4OSwxOS4xMTIgNjUuNTkxLDE5LjEyNiBDNjUuOTg1LDE5LjgzOCA2Ni40NjksMjAuNDk3IDY3LjAzLDIxLjA4NSBMNjcuMzA1LDIxLjM1MSBDNjkuMTUxLDIzLjEzNyA3MS42NDksMjQuMTIgNzQuMzM2LDI0LjEyIEM3Ni4zMTMsMjQuMTIgNzguMjksMjMuNTgyIDgwLjA1MywyMi41NjMgQzgwLjA2NCwyMi41NTcgODAuMDc2LDIyLjU1MyA4MC4wODgsMjIuNTUgTDg3LjM3MiwxOC4zNDQgQzg4LjAzOCwxNy45NTkgODguNzg0LDE3Ljc1NiA4OS41MjksMTcuNzU2IEM5MC45NTYsMTcuNzU2IDkyLjIwMSwxOC40NzIgOTIuODU4LDE5LjY3IEw5Ni4xMDcsMjUuNTk5IEM5Ni4xMzgsMjUuNjU0IDk2LjExOCwyNS43MjQgOTYuMDYzLDI1Ljc1NiBMMTIuNTQ1LDczLjk4NSBDMTIuNTI2LDczLjk5NiAxMi41MDYsNzQuMDAxIDEyLjQ4Niw3NC4wMDEgTDEyLjQ4Niw3NC4wMDEgWiBNNjIuMDE2LDE2Ljk5NyBDNjEuMzEyLDE2Ljk5NyA2MC42MDYsMTcuMTkgNTkuOTc1LDE3LjU1NCBMMy45NjUsNDkuODk5IEMyLjA4Myw1MC45ODUgMS4zNDEsNTMuMzA4IDIuMzEsNTUuMDc4IEwxMi41MzEsNzMuNzIzIEw5NS44NDgsMjUuNjExIEw5Mi42NTMsMTkuNzgyIEM5Mi4wMzgsMTguNjYgOTAuODcsMTcuOTkgODkuNTI5LDE3Ljk5IEM4OC44MjUsMTcuOTkgODguMTE5LDE4LjE4MiA4Ny40ODksMTguNTQ3IEw4MC4xNzIsMjIuNzcyIEM4MC4xNjEsMjIuNzc4IDgwLjE0OSwyMi43ODIgODAuMTM3LDIyLjc4NSBDNzguMzQ2LDIzLjgxMSA3Ni4zNDEsMjQuMzU0IDc0LjMzNiwyNC4zNTQgQzcxLjU4OCwyNC4zNTQgNjkuMDMzLDIzLjM0NyA2Ny4xNDIsMjEuNTE5IEw2Ni44NjQsMjEuMjQ5IEM2Ni4yNzcsMjAuNjM0IDY1Ljc3NCwxOS45NDcgNjUuMzY3LDE5LjIwMyBDNjUuMzYsMTkuMTkyIDY1LjM1NiwxOS4xNzkgNjUuMzU0LDE5LjE2NiBMNjUuMTYzLDE4LjgxOSBDNjUuMTU0LDE4LjgxMSA2NS4xNDYsMTguODAxIDY1LjE0LDE4Ljc5IEM2NC41MjUsMTcuNjY3IDYzLjM1NywxNi45OTcgNjIuMDE2LDE2Ljk5NyBMNjIuMDE2LDE2Ljk5NyBaIiBpZD0iRmlsbC03IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTQyLjQzNCw0OC44MDggTDQyLjQzNCw0OC44MDggQzM5LjkyNCw0OC44MDcgMzcuNzM3LDQ3LjU1IDM2LjU4Miw0NS40NDMgQzM0Ljc3MSw0Mi4xMzkgMzYuMTQ0LDM3LjgwOSAzOS42NDEsMzUuNzg5IEw1MS45MzIsMjguNjkxIEM1My4xMDMsMjguMDE1IDU0LjQxMywyNy42NTggNTUuNzIxLDI3LjY1OCBDNTguMjMxLDI3LjY1OCA2MC40MTgsMjguOTE2IDYxLjU3MywzMS4wMjMgQzYzLjM4NCwzNC4zMjcgNjIuMDEyLDM4LjY1NyA1OC41MTQsNDAuNjc3IEw0Ni4yMjMsNDcuNzc1IEM0NS4wNTMsNDguNDUgNDMuNzQyLDQ4LjgwOCA0Mi40MzQsNDguODA4IEw0Mi40MzQsNDguODA4IFogTTU1LjcyMSwyOC4xMjUgQzU0LjQ5NSwyOC4xMjUgNTMuMjY1LDI4LjQ2MSA1Mi4xNjYsMjkuMDk2IEwzOS44NzUsMzYuMTk0IEMzNi41OTYsMzguMDg3IDM1LjMwMiw0Mi4xMzYgMzYuOTkyLDQ1LjIxOCBDMzguMDYzLDQ3LjE3MyA0MC4wOTgsNDguMzQgNDIuNDM0LDQ4LjM0IEM0My42NjEsNDguMzQgNDQuODksNDguMDA1IDQ1Ljk5LDQ3LjM3IEw1OC4yODEsNDAuMjcyIEM2MS41NiwzOC4zNzkgNjIuODUzLDM0LjMzIDYxLjE2NCwzMS4yNDggQzYwLjA5MiwyOS4yOTMgNTguMDU4LDI4LjEyNSA1NS43MjEsMjguMTI1IEw1NS43MjEsMjguMTI1IFoiIGlkPSJGaWxsLTgiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQ5LjU4OCwyLjQwNyBDMTQ5LjU4OCwyLjQwNyAxNTUuNzY4LDUuOTc1IDE1Ni4zMjUsNi4yOTcgTDE1Ni4zMjUsNy4xODQgQzE1Ni4zMjUsNy4zNiAxNTYuMzM4LDcuNTQ0IDE1Ni4zNjIsNy43MzMgQzE1Ni4zNzMsNy44MTQgMTU2LjM4Miw3Ljg5NCAxNTYuMzksNy45NzUgQzE1Ni41Myw5LjM5IDE1Ny4zNjMsMTAuOTczIDE1OC40OTUsMTEuOTc0IEwxNjUuODkxLDE4LjUxOSBDMTY2LjA2OCwxOC42NzUgMTY2LjI0OSwxOC44MTQgMTY2LjQzMiwxOC45MzQgQzE2OC4wMTEsMTkuOTc0IDE2OS4zODIsMTkuNCAxNjkuNDk0LDE3LjY1MiBDMTY5LjU0MywxNi44NjggMTY5LjU1MSwxNi4wNTcgMTY5LjUxNywxNS4yMjMgTDE2OS41MTQsMTUuMDYzIEwxNjkuNTE0LDEzLjkxMiBDMTcwLjc4LDE0LjY0MiAxOTUuNTAxLDI4LjkxNSAxOTUuNTAxLDI4LjkxNSBMMTk1LjUwMSw4Mi45MTUgQzE5NS41MDEsODQuMDA1IDE5NC43MzEsODQuNDQ1IDE5My43ODEsODMuODk3IEwxNTEuMzA4LDU5LjM3NCBDMTUwLjM1OCw1OC44MjYgMTQ5LjU4OCw1Ny40OTcgMTQ5LjU4OCw1Ni40MDggTDE0OS41ODgsMjIuMzc1IiBpZD0iRmlsbC05IiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE5NC41NTMsODQuMjUgQzE5NC4yOTYsODQuMjUgMTk0LjAxMyw4NC4xNjUgMTkzLjcyMiw4My45OTcgTDE1MS4yNSw1OS40NzYgQzE1MC4yNjksNTguOTA5IDE0OS40NzEsNTcuNTMzIDE0OS40NzEsNTYuNDA4IEwxNDkuNDcxLDIyLjM3NSBMMTQ5LjcwNSwyMi4zNzUgTDE0OS43MDUsNTYuNDA4IEMxNDkuNzA1LDU3LjQ1OSAxNTAuNDUsNTguNzQ0IDE1MS4zNjYsNTkuMjc0IEwxOTMuODM5LDgzLjc5NSBDMTk0LjI2Myw4NC4wNCAxOTQuNjU1LDg0LjA4MyAxOTQuOTQyLDgzLjkxNyBDMTk1LjIyNyw4My43NTMgMTk1LjM4NCw4My4zOTcgMTk1LjM4NCw4Mi45MTUgTDE5NS4zODQsMjguOTgyIEMxOTQuMTAyLDI4LjI0MiAxNzIuMTA0LDE1LjU0MiAxNjkuNjMxLDE0LjExNCBMMTY5LjYzNCwxNS4yMiBDMTY5LjY2OCwxNi4wNTIgMTY5LjY2LDE2Ljg3NCAxNjkuNjEsMTcuNjU5IEMxNjkuNTU2LDE4LjUwMyAxNjkuMjE0LDE5LjEyMyAxNjguNjQ3LDE5LjQwNSBDMTY4LjAyOCwxOS43MTQgMTY3LjE5NywxOS41NzggMTY2LjM2NywxOS4wMzIgQzE2Ni4xODEsMTguOTA5IDE2NS45OTUsMTguNzY2IDE2NS44MTQsMTguNjA2IEwxNTguNDE3LDEyLjA2MiBDMTU3LjI1OSwxMS4wMzYgMTU2LjQxOCw5LjQzNyAxNTYuMjc0LDcuOTg2IEMxNTYuMjY2LDcuOTA3IDE1Ni4yNTcsNy44MjcgMTU2LjI0Nyw3Ljc0OCBDMTU2LjIyMSw3LjU1NSAxNTYuMjA5LDcuMzY1IDE1Ni4yMDksNy4xODQgTDE1Ni4yMDksNi4zNjQgQzE1NS4zNzUsNS44ODMgMTQ5LjUyOSwyLjUwOCAxNDkuNTI5LDIuNTA4IEwxNDkuNjQ2LDIuMzA2IEMxNDkuNjQ2LDIuMzA2IDE1NS44MjcsNS44NzQgMTU2LjM4NCw2LjE5NiBMMTU2LjQ0Miw2LjIzIEwxNTYuNDQyLDcuMTg0IEMxNTYuNDQyLDcuMzU1IDE1Ni40NTQsNy41MzUgMTU2LjQ3OCw3LjcxNyBDMTU2LjQ4OSw3LjggMTU2LjQ5OSw3Ljg4MiAxNTYuNTA3LDcuOTYzIEMxNTYuNjQ1LDkuMzU4IDE1Ny40NTUsMTAuODk4IDE1OC41NzIsMTEuODg2IEwxNjUuOTY5LDE4LjQzMSBDMTY2LjE0MiwxOC41ODQgMTY2LjMxOSwxOC43MiAxNjYuNDk2LDE4LjgzNyBDMTY3LjI1NCwxOS4zMzYgMTY4LDE5LjQ2NyAxNjguNTQzLDE5LjE5NiBDMTY5LjAzMywxOC45NTMgMTY5LjMyOSwxOC40MDEgMTY5LjM3NywxNy42NDUgQzE2OS40MjcsMTYuODY3IDE2OS40MzQsMTYuMDU0IDE2OS40MDEsMTUuMjI4IEwxNjkuMzk3LDE1LjA2NSBMMTY5LjM5NywxMy43MSBMMTY5LjU3MiwxMy44MSBDMTcwLjgzOSwxNC41NDEgMTk1LjU1OSwyOC44MTQgMTk1LjU1OSwyOC44MTQgTDE5NS42MTgsMjguODQ3IEwxOTUuNjE4LDgyLjkxNSBDMTk1LjYxOCw4My40ODQgMTk1LjQyLDgzLjkxMSAxOTUuMDU5LDg0LjExOSBDMTk0LjkwOCw4NC4yMDYgMTk0LjczNyw4NC4yNSAxOTQuNTUzLDg0LjI1IiBpZD0iRmlsbC0xMCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNDUuNjg1LDU2LjE2MSBMMTY5LjgsNzAuMDgzIEwxNDMuODIyLDg1LjA4MSBMMTQyLjM2LDg0Ljc3NCBDMTM1LjgyNiw4Mi42MDQgMTI4LjczMiw4MS4wNDYgMTIxLjM0MSw4MC4xNTggQzExNi45NzYsNzkuNjM0IDExMi42NzgsODEuMjU0IDExMS43NDMsODMuNzc4IEMxMTEuNTA2LDg0LjQxNCAxMTEuNTAzLDg1LjA3MSAxMTEuNzMyLDg1LjcwNiBDMTEzLjI3LDg5Ljk3MyAxMTUuOTY4LDk0LjA2OSAxMTkuNzI3LDk3Ljg0MSBMMTIwLjI1OSw5OC42ODYgQzEyMC4yNiw5OC42ODUgOTQuMjgyLDExMy42ODMgOTQuMjgyLDExMy42ODMgTDcwLjE2Nyw5OS43NjEgTDE0NS42ODUsNTYuMTYxIiBpZD0iRmlsbC0xMSIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik05NC4yODIsMTEzLjgxOCBMOTQuMjIzLDExMy43ODUgTDY5LjkzMyw5OS43NjEgTDcwLjEwOCw5OS42NiBMMTQ1LjY4NSw1Ni4wMjYgTDE0NS43NDMsNTYuMDU5IEwxNzAuMDMzLDcwLjA4MyBMMTQzLjg0Miw4NS4yMDUgTDE0My43OTcsODUuMTk1IEMxNDMuNzcyLDg1LjE5IDE0Mi4zMzYsODQuODg4IDE0Mi4zMzYsODQuODg4IEMxMzUuNzg3LDgyLjcxNCAxMjguNzIzLDgxLjE2MyAxMjEuMzI3LDgwLjI3NCBDMTIwLjc4OCw4MC4yMDkgMTIwLjIzNiw4MC4xNzcgMTE5LjY4OSw4MC4xNzcgQzExNS45MzEsODAuMTc3IDExMi42MzUsODEuNzA4IDExMS44NTIsODMuODE5IEMxMTEuNjI0LDg0LjQzMiAxMTEuNjIxLDg1LjA1MyAxMTEuODQyLDg1LjY2NyBDMTEzLjM3Nyw4OS45MjUgMTE2LjA1OCw5My45OTMgMTE5LjgxLDk3Ljc1OCBMMTE5LjgyNiw5Ny43NzkgTDEyMC4zNTIsOTguNjE0IEMxMjAuMzU0LDk4LjYxNyAxMjAuMzU2LDk4LjYyIDEyMC4zNTgsOTguNjI0IEwxMjAuNDIyLDk4LjcyNiBMMTIwLjMxNyw5OC43ODcgQzEyMC4yNjQsOTguODE4IDk0LjU5OSwxMTMuNjM1IDk0LjM0LDExMy43ODUgTDk0LjI4MiwxMTMuODE4IEw5NC4yODIsMTEzLjgxOCBaIE03MC40MDEsOTkuNzYxIEw5NC4yODIsMTEzLjU0OSBMMTE5LjA4NCw5OS4yMjkgQzExOS42Myw5OC45MTQgMTE5LjkzLDk4Ljc0IDEyMC4xMDEsOTguNjU0IEwxMTkuNjM1LDk3LjkxNCBDMTE1Ljg2NCw5NC4xMjcgMTEzLjE2OCw5MC4wMzMgMTExLjYyMiw4NS43NDYgQzExMS4zODIsODUuMDc5IDExMS4zODYsODQuNDA0IDExMS42MzMsODMuNzM4IEMxMTIuNDQ4LDgxLjUzOSAxMTUuODM2LDc5Ljk0MyAxMTkuNjg5LDc5Ljk0MyBDMTIwLjI0Niw3OS45NDMgMTIwLjgwNiw3OS45NzYgMTIxLjM1NSw4MC4wNDIgQzEyOC43NjcsODAuOTMzIDEzNS44NDYsODIuNDg3IDE0Mi4zOTYsODQuNjYzIEMxNDMuMjMyLDg0LjgzOCAxNDMuNjExLDg0LjkxNyAxNDMuNzg2LDg0Ljk2NyBMMTY5LjU2Niw3MC4wODMgTDE0NS42ODUsNTYuMjk1IEw3MC40MDEsOTkuNzYxIEw3MC40MDEsOTkuNzYxIFoiIGlkPSJGaWxsLTEyIiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2Ny4yMywxOC45NzkgTDE2Ny4yMyw2OS44NSBMMTM5LjkwOSw4NS42MjMgTDEzMy40NDgsNzEuNDU2IEMxMzIuNTM4LDY5LjQ2IDEzMC4wMiw2OS43MTggMTI3LjgyNCw3Mi4wMyBDMTI2Ljc2OSw3My4xNCAxMjUuOTMxLDc0LjU4NSAxMjUuNDk0LDc2LjA0OCBMMTE5LjAzNCw5Ny42NzYgTDkxLjcxMiwxMTMuNDUgTDkxLjcxMiw2Mi41NzkgTDE2Ny4yMywxOC45NzkiIGlkPSJGaWxsLTEzIiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTkxLjcxMiwxMTMuNTY3IEM5MS42OTIsMTEzLjU2NyA5MS42NzIsMTEzLjU2MSA5MS42NTMsMTEzLjU1MSBDOTEuNjE4LDExMy41MyA5MS41OTUsMTEzLjQ5MiA5MS41OTUsMTEzLjQ1IEw5MS41OTUsNjIuNTc5IEM5MS41OTUsNjIuNTM3IDkxLjYxOCw2Mi40OTkgOTEuNjUzLDYyLjQ3OCBMMTY3LjE3MiwxOC44NzggQzE2Ny4yMDgsMTguODU3IDE2Ny4yNTIsMTguODU3IDE2Ny4yODgsMTguODc4IEMxNjcuMzI0LDE4Ljg5OSAxNjcuMzQ3LDE4LjkzNyAxNjcuMzQ3LDE4Ljk3OSBMMTY3LjM0Nyw2OS44NSBDMTY3LjM0Nyw2OS44OTEgMTY3LjMyNCw2OS45MyAxNjcuMjg4LDY5Ljk1IEwxMzkuOTY3LDg1LjcyNSBDMTM5LjkzOSw4NS43NDEgMTM5LjkwNSw4NS43NDUgMTM5Ljg3Myw4NS43MzUgQzEzOS44NDIsODUuNzI1IDEzOS44MTYsODUuNzAyIDEzOS44MDIsODUuNjcyIEwxMzMuMzQyLDcxLjUwNCBDMTMyLjk2Nyw3MC42ODIgMTMyLjI4LDcwLjIyOSAxMzEuNDA4LDcwLjIyOSBDMTMwLjMxOSw3MC4yMjkgMTI5LjA0NCw3MC45MTUgMTI3LjkwOCw3Mi4xMSBDMTI2Ljg3NCw3My4yIDEyNi4wMzQsNzQuNjQ3IDEyNS42MDYsNzYuMDgyIEwxMTkuMTQ2LDk3LjcwOSBDMTE5LjEzNyw5Ny43MzggMTE5LjExOCw5Ny43NjIgMTE5LjA5Miw5Ny43NzcgTDkxLjc3LDExMy41NTEgQzkxLjc1MiwxMTMuNTYxIDkxLjczMiwxMTMuNTY3IDkxLjcxMiwxMTMuNTY3IEw5MS43MTIsMTEzLjU2NyBaIE05MS44MjksNjIuNjQ3IEw5MS44MjksMTEzLjI0OCBMMTE4LjkzNSw5Ny41OTggTDEyNS4zODIsNzYuMDE1IEMxMjUuODI3LDc0LjUyNSAxMjYuNjY0LDczLjA4MSAxMjcuNzM5LDcxLjk1IEMxMjguOTE5LDcwLjcwOCAxMzAuMjU2LDY5Ljk5NiAxMzEuNDA4LDY5Ljk5NiBDMTMyLjM3Nyw2OS45OTYgMTMzLjEzOSw3MC40OTcgMTMzLjU1NCw3MS40MDcgTDEzOS45NjEsODUuNDU4IEwxNjcuMTEzLDY5Ljc4MiBMMTY3LjExMywxOS4xODEgTDkxLjgyOSw2Mi42NDcgTDkxLjgyOSw2Mi42NDcgWiIgaWQ9IkZpbGwtMTQiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTY4LjU0MywxOS4yMTMgTDE2OC41NDMsNzAuMDgzIEwxNDEuMjIxLDg1Ljg1NyBMMTM0Ljc2MSw3MS42ODkgQzEzMy44NTEsNjkuNjk0IDEzMS4zMzMsNjkuOTUxIDEyOS4xMzcsNzIuMjYzIEMxMjguMDgyLDczLjM3NCAxMjcuMjQ0LDc0LjgxOSAxMjYuODA3LDc2LjI4MiBMMTIwLjM0Niw5Ny45MDkgTDkzLjAyNSwxMTMuNjgzIEw5My4wMjUsNjIuODEzIEwxNjguNTQzLDE5LjIxMyIgaWQ9IkZpbGwtMTUiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTMuMDI1LDExMy44IEM5My4wMDUsMTEzLjggOTIuOTg0LDExMy43OTUgOTIuOTY2LDExMy43ODUgQzkyLjkzMSwxMTMuNzY0IDkyLjkwOCwxMTMuNzI1IDkyLjkwOCwxMTMuNjg0IEw5Mi45MDgsNjIuODEzIEM5Mi45MDgsNjIuNzcxIDkyLjkzMSw2Mi43MzMgOTIuOTY2LDYyLjcxMiBMMTY4LjQ4NCwxOS4xMTIgQzE2OC41MiwxOS4wOSAxNjguNTY1LDE5LjA5IDE2OC42MDEsMTkuMTEyIEMxNjguNjM3LDE5LjEzMiAxNjguNjYsMTkuMTcxIDE2OC42NiwxOS4yMTIgTDE2OC42Niw3MC4wODMgQzE2OC42Niw3MC4xMjUgMTY4LjYzNyw3MC4xNjQgMTY4LjYwMSw3MC4xODQgTDE0MS4yOCw4NS45NTggQzE0MS4yNTEsODUuOTc1IDE0MS4yMTcsODUuOTc5IDE0MS4xODYsODUuOTY4IEMxNDEuMTU0LDg1Ljk1OCAxNDEuMTI5LDg1LjkzNiAxNDEuMTE1LDg1LjkwNiBMMTM0LjY1NSw3MS43MzggQzEzNC4yOCw3MC45MTUgMTMzLjU5Myw3MC40NjMgMTMyLjcyLDcwLjQ2MyBDMTMxLjYzMiw3MC40NjMgMTMwLjM1Nyw3MS4xNDggMTI5LjIyMSw3Mi4zNDQgQzEyOC4xODYsNzMuNDMzIDEyNy4zNDcsNzQuODgxIDEyNi45MTksNzYuMzE1IEwxMjAuNDU4LDk3Ljk0MyBDMTIwLjQ1LDk3Ljk3MiAxMjAuNDMxLDk3Ljk5NiAxMjAuNDA1LDk4LjAxIEw5My4wODMsMTEzLjc4NSBDOTMuMDY1LDExMy43OTUgOTMuMDQ1LDExMy44IDkzLjAyNSwxMTMuOCBMOTMuMDI1LDExMy44IFogTTkzLjE0Miw2Mi44ODEgTDkzLjE0MiwxMTMuNDgxIEwxMjAuMjQ4LDk3LjgzMiBMMTI2LjY5NSw3Ni4yNDggQzEyNy4xNCw3NC43NTggMTI3Ljk3Nyw3My4zMTUgMTI5LjA1Miw3Mi4xODMgQzEzMC4yMzEsNzAuOTQyIDEzMS41NjgsNzAuMjI5IDEzMi43Miw3MC4yMjkgQzEzMy42ODksNzAuMjI5IDEzNC40NTIsNzAuNzMxIDEzNC44NjcsNzEuNjQxIEwxNDEuMjc0LDg1LjY5MiBMMTY4LjQyNiw3MC4wMTYgTDE2OC40MjYsMTkuNDE1IEw5My4xNDIsNjIuODgxIEw5My4xNDIsNjIuODgxIFoiIGlkPSJGaWxsLTE2IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2OS44LDcwLjA4MyBMMTQyLjQ3OCw4NS44NTcgTDEzNi4wMTgsNzEuNjg5IEMxMzUuMTA4LDY5LjY5NCAxMzIuNTksNjkuOTUxIDEzMC4zOTMsNzIuMjYzIEMxMjkuMzM5LDczLjM3NCAxMjguNSw3NC44MTkgMTI4LjA2NCw3Ni4yODIgTDEyMS42MDMsOTcuOTA5IEw5NC4yODIsMTEzLjY4MyBMOTQuMjgyLDYyLjgxMyBMMTY5LjgsMTkuMjEzIEwxNjkuOCw3MC4wODMgWiIgaWQ9IkZpbGwtMTciIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTQuMjgyLDExMy45MTcgQzk0LjI0MSwxMTMuOTE3IDk0LjIwMSwxMTMuOTA3IDk0LjE2NSwxMTMuODg2IEM5NC4wOTMsMTEzLjg0NSA5NC4wNDgsMTEzLjc2NyA5NC4wNDgsMTEzLjY4NCBMOTQuMDQ4LDYyLjgxMyBDOTQuMDQ4LDYyLjczIDk0LjA5Myw2Mi42NTIgOTQuMTY1LDYyLjYxMSBMMTY5LjY4MywxOS4wMSBDMTY5Ljc1NSwxOC45NjkgMTY5Ljg0NCwxOC45NjkgMTY5LjkxNywxOS4wMSBDMTY5Ljk4OSwxOS4wNTIgMTcwLjAzMywxOS4xMjkgMTcwLjAzMywxOS4yMTIgTDE3MC4wMzMsNzAuMDgzIEMxNzAuMDMzLDcwLjE2NiAxNjkuOTg5LDcwLjI0NCAxNjkuOTE3LDcwLjI4NSBMMTQyLjU5NSw4Ni4wNiBDMTQyLjUzOCw4Ni4wOTIgMTQyLjQ2OSw4Ni4xIDE0Mi40MDcsODYuMDggQzE0Mi4zNDQsODYuMDYgMTQyLjI5Myw4Ni4wMTQgMTQyLjI2Niw4NS45NTQgTDEzNS44MDUsNzEuNzg2IEMxMzUuNDQ1LDcwLjk5NyAxMzQuODEzLDcwLjU4IDEzMy45NzcsNzAuNTggQzEzMi45MjEsNzAuNTggMTMxLjY3Niw3MS4yNTIgMTMwLjU2Miw3Mi40MjQgQzEyOS41NCw3My41MDEgMTI4LjcxMSw3NC45MzEgMTI4LjI4Nyw3Ni4zNDggTDEyMS44MjcsOTcuOTc2IEMxMjEuODEsOTguMDM0IDEyMS43NzEsOTguMDgyIDEyMS43Miw5OC4xMTIgTDk0LjM5OCwxMTMuODg2IEM5NC4zNjIsMTEzLjkwNyA5NC4zMjIsMTEzLjkxNyA5NC4yODIsMTEzLjkxNyBMOTQuMjgyLDExMy45MTcgWiBNOTQuNTE1LDYyLjk0OCBMOTQuNTE1LDExMy4yNzkgTDEyMS40MDYsOTcuNzU0IEwxMjcuODQsNzYuMjE1IEMxMjguMjksNzQuNzA4IDEyOS4xMzcsNzMuMjQ3IDEzMC4yMjQsNzIuMTAzIEMxMzEuNDI1LDcwLjgzOCAxMzIuNzkzLDcwLjExMiAxMzMuOTc3LDcwLjExMiBDMTM0Ljk5NSw3MC4xMTIgMTM1Ljc5NSw3MC42MzggMTM2LjIzLDcxLjU5MiBMMTQyLjU4NCw4NS41MjYgTDE2OS41NjYsNjkuOTQ4IEwxNjkuNTY2LDE5LjYxNyBMOTQuNTE1LDYyLjk0OCBMOTQuNTE1LDYyLjk0OCBaIiBpZD0iRmlsbC0xOCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMDkuODk0LDkyLjk0MyBMMTA5Ljg5NCw5Mi45NDMgQzEwOC4xMiw5Mi45NDMgMTA2LjY1Myw5Mi4yMTggMTA1LjY1LDkwLjgyMyBDMTA1LjU4Myw5MC43MzEgMTA1LjU5Myw5MC42MSAxMDUuNjczLDkwLjUyOSBDMTA1Ljc1Myw5MC40NDggMTA1Ljg4LDkwLjQ0IDEwNS45NzQsOTAuNTA2IEMxMDYuNzU0LDkxLjA1MyAxMDcuNjc5LDkxLjMzMyAxMDguNzI0LDkxLjMzMyBDMTEwLjA0Nyw5MS4zMzMgMTExLjQ3OCw5MC44OTQgMTEyLjk4LDkwLjAyNyBDMTE4LjI5MSw4Ni45NiAxMjIuNjExLDc5LjUwOSAxMjIuNjExLDczLjQxNiBDMTIyLjYxMSw3MS40ODkgMTIyLjE2OSw2OS44NTYgMTIxLjMzMyw2OC42OTIgQzEyMS4yNjYsNjguNiAxMjEuMjc2LDY4LjQ3MyAxMjEuMzU2LDY4LjM5MiBDMTIxLjQzNiw2OC4zMTEgMTIxLjU2Myw2OC4yOTkgMTIxLjY1Niw2OC4zNjUgQzEyMy4zMjcsNjkuNTM3IDEyNC4yNDcsNzEuNzQ2IDEyNC4yNDcsNzQuNTg0IEMxMjQuMjQ3LDgwLjgyNiAxMTkuODIxLDg4LjQ0NyAxMTQuMzgyLDkxLjU4NyBDMTEyLjgwOCw5Mi40OTUgMTExLjI5OCw5Mi45NDMgMTA5Ljg5NCw5Mi45NDMgTDEwOS44OTQsOTIuOTQzIFogTTEwNi45MjUsOTEuNDAxIEMxMDcuNzM4LDkyLjA1MiAxMDguNzQ1LDkyLjI3OCAxMDkuODkzLDkyLjI3OCBMMTA5Ljg5NCw5Mi4yNzggQzExMS4yMTUsOTIuMjc4IDExMi42NDcsOTEuOTUxIDExNC4xNDgsOTEuMDg0IEMxMTkuNDU5LDg4LjAxNyAxMjMuNzgsODAuNjIxIDEyMy43OCw3NC41MjggQzEyMy43OCw3Mi41NDkgMTIzLjMxNyw3MC45MjkgMTIyLjQ1NCw2OS43NjcgQzEyMi44NjUsNzAuODAyIDEyMy4wNzksNzIuMDQyIDEyMy4wNzksNzMuNDAyIEMxMjMuMDc5LDc5LjY0NSAxMTguNjUzLDg3LjI4NSAxMTMuMjE0LDkwLjQyNSBDMTExLjY0LDkxLjMzNCAxMTAuMTMsOTEuNzQyIDEwOC43MjQsOTEuNzQyIEMxMDguMDgzLDkxLjc0MiAxMDcuNDgxLDkxLjU5MyAxMDYuOTI1LDkxLjQwMSBMMTA2LjkyNSw5MS40MDEgWiIgaWQ9IkZpbGwtMTkiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEzLjA5Nyw5MC4yMyBDMTE4LjQ4MSw4Ny4xMjIgMTIyLjg0NSw3OS41OTQgMTIyLjg0NSw3My40MTYgQzEyMi44NDUsNzEuMzY1IDEyMi4zNjIsNjkuNzI0IDEyMS41MjIsNjguNTU2IEMxMTkuNzM4LDY3LjMwNCAxMTcuMTQ4LDY3LjM2MiAxMTQuMjY1LDY5LjAyNiBDMTA4Ljg4MSw3Mi4xMzQgMTA0LjUxNyw3OS42NjIgMTA0LjUxNyw4NS44NCBDMTA0LjUxNyw4Ny44OTEgMTA1LDg5LjUzMiAxMDUuODQsOTAuNyBDMTA3LjYyNCw5MS45NTIgMTEwLjIxNCw5MS44OTQgMTEzLjA5Nyw5MC4yMyIgaWQ9IkZpbGwtMjAiIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTA4LjcyNCw5MS42MTQgTDEwOC43MjQsOTEuNjE0IEMxMDcuNTgyLDkxLjYxNCAxMDYuNTY2LDkxLjQwMSAxMDUuNzA1LDkwLjc5NyBDMTA1LjY4NCw5MC43ODMgMTA1LjY2NSw5MC44MTEgMTA1LjY1LDkwLjc5IEMxMDQuNzU2LDg5LjU0NiAxMDQuMjgzLDg3Ljg0MiAxMDQuMjgzLDg1LjgxNyBDMTA0LjI4Myw3OS41NzUgMTA4LjcwOSw3MS45NTMgMTE0LjE0OCw2OC44MTIgQzExNS43MjIsNjcuOTA0IDExNy4yMzIsNjcuNDQ5IDExOC42MzgsNjcuNDQ5IEMxMTkuNzgsNjcuNDQ5IDEyMC43OTYsNjcuNzU4IDEyMS42NTYsNjguMzYyIEMxMjEuNjc4LDY4LjM3NyAxMjEuNjk3LDY4LjM5NyAxMjEuNzEyLDY4LjQxOCBDMTIyLjYwNiw2OS42NjIgMTIzLjA3OSw3MS4zOSAxMjMuMDc5LDczLjQxNSBDMTIzLjA3OSw3OS42NTggMTE4LjY1Myw4Ny4xOTggMTEzLjIxNCw5MC4zMzggQzExMS42NCw5MS4yNDcgMTEwLjEzLDkxLjYxNCAxMDguNzI0LDkxLjYxNCBMMTA4LjcyNCw5MS42MTQgWiBNMTA2LjAwNiw5MC41MDUgQzEwNi43OCw5MS4wMzcgMTA3LjY5NCw5MS4yODEgMTA4LjcyNCw5MS4yODEgQzExMC4wNDcsOTEuMjgxIDExMS40NzgsOTAuODY4IDExMi45OCw5MC4wMDEgQzExOC4yOTEsODYuOTM1IDEyMi42MTEsNzkuNDk2IDEyMi42MTEsNzMuNDAzIEMxMjIuNjExLDcxLjQ5NCAxMjIuMTc3LDY5Ljg4IDEyMS4zNTYsNjguNzE4IEMxMjAuNTgyLDY4LjE4NSAxMTkuNjY4LDY3LjkxOSAxMTguNjM4LDY3LjkxOSBDMTE3LjMxNSw2Ny45MTkgMTE1Ljg4Myw2OC4zNiAxMTQuMzgyLDY5LjIyNyBDMTA5LjA3MSw3Mi4yOTMgMTA0Ljc1MSw3OS43MzMgMTA0Ljc1MSw4NS44MjYgQzEwNC43NTEsODcuNzM1IDEwNS4xODUsODkuMzQzIDEwNi4wMDYsOTAuNTA1IEwxMDYuMDA2LDkwLjUwNSBaIiBpZD0iRmlsbC0yMSIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNDkuMzE4LDcuMjYyIEwxMzkuMzM0LDE2LjE0IEwxNTUuMjI3LDI3LjE3MSBMMTYwLjgxNiwyMS4wNTkgTDE0OS4zMTgsNy4yNjIiIGlkPSJGaWxsLTIyIiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2OS42NzYsMTMuODQgTDE1OS45MjgsMTkuNDY3IEMxNTYuMjg2LDIxLjU3IDE1MC40LDIxLjU4IDE0Ni43ODEsMTkuNDkxIEMxNDMuMTYxLDE3LjQwMiAxNDMuMTgsMTQuMDAzIDE0Ni44MjIsMTEuOSBMMTU2LjMxNyw2LjI5MiBMMTQ5LjU4OCwyLjQwNyBMNjcuNzUyLDQ5LjQ3OCBMMTEzLjY3NSw3NS45OTIgTDExNi43NTYsNzQuMjEzIEMxMTcuMzg3LDczLjg0OCAxMTcuNjI1LDczLjMxNSAxMTcuMzc0LDcyLjgyMyBDMTE1LjAxNyw2OC4xOTEgMTE0Ljc4MSw2My4yNzcgMTE2LjY5MSw1OC41NjEgQzEyMi4zMjksNDQuNjQxIDE0MS4yLDMzLjc0NiAxNjUuMzA5LDMwLjQ5MSBDMTczLjQ3OCwyOS4zODggMTgxLjk4OSwyOS41MjQgMTkwLjAxMywzMC44ODUgQzE5MC44NjUsMzEuMDMgMTkxLjc4OSwzMC44OTMgMTkyLjQyLDMwLjUyOCBMMTk1LjUwMSwyOC43NSBMMTY5LjY3NiwxMy44NCIgaWQ9IkZpbGwtMjMiIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEzLjY3NSw3Ni40NTkgQzExMy41OTQsNzYuNDU5IDExMy41MTQsNzYuNDM4IDExMy40NDIsNzYuMzk3IEw2Ny41MTgsNDkuODgyIEM2Ny4zNzQsNDkuNzk5IDY3LjI4NCw0OS42NDUgNjcuMjg1LDQ5LjQ3OCBDNjcuMjg1LDQ5LjMxMSA2Ny4zNzQsNDkuMTU3IDY3LjUxOSw0OS4wNzMgTDE0OS4zNTUsMi4wMDIgQzE0OS40OTksMS45MTkgMTQ5LjY3NywxLjkxOSAxNDkuODIxLDIuMDAyIEwxNTYuNTUsNS44ODcgQzE1Ni43NzQsNi4wMTcgMTU2Ljg1LDYuMzAyIDE1Ni43MjIsNi41MjYgQzE1Ni41OTIsNi43NDkgMTU2LjMwNyw2LjgyNiAxNTYuMDgzLDYuNjk2IEwxNDkuNTg3LDIuOTQ2IEw2OC42ODcsNDkuNDc5IEwxMTMuNjc1LDc1LjQ1MiBMMTE2LjUyMyw3My44MDggQzExNi43MTUsNzMuNjk3IDExNy4xNDMsNzMuMzk5IDExNi45NTgsNzMuMDM1IEMxMTQuNTQyLDY4LjI4NyAxMTQuMyw2My4yMjEgMTE2LjI1OCw1OC4zODUgQzExOS4wNjQsNTEuNDU4IDEyNS4xNDMsNDUuMTQzIDEzMy44NCw0MC4xMjIgQzE0Mi40OTcsMzUuMTI0IDE1My4zNTgsMzEuNjMzIDE2NS4yNDcsMzAuMDI4IEMxNzMuNDQ1LDI4LjkyMSAxODIuMDM3LDI5LjA1OCAxOTAuMDkxLDMwLjQyNSBDMTkwLjgzLDMwLjU1IDE5MS42NTIsMzAuNDMyIDE5Mi4xODYsMzAuMTI0IEwxOTQuNTY3LDI4Ljc1IEwxNjkuNDQyLDE0LjI0NCBDMTY5LjIxOSwxNC4xMTUgMTY5LjE0MiwxMy44MjkgMTY5LjI3MSwxMy42MDYgQzE2OS40LDEzLjM4MiAxNjkuNjg1LDEzLjMwNiAxNjkuOTA5LDEzLjQzNSBMMTk1LjczNCwyOC4zNDUgQzE5NS44NzksMjguNDI4IDE5NS45NjgsMjguNTgzIDE5NS45NjgsMjguNzUgQzE5NS45NjgsMjguOTE2IDE5NS44NzksMjkuMDcxIDE5NS43MzQsMjkuMTU0IEwxOTIuNjUzLDMwLjkzMyBDMTkxLjkzMiwzMS4zNSAxOTAuODksMzEuNTA4IDE4OS45MzUsMzEuMzQ2IEMxODEuOTcyLDI5Ljk5NSAxNzMuNDc4LDI5Ljg2IDE2NS4zNzIsMzAuOTU0IEMxNTMuNjAyLDMyLjU0MyAxNDIuODYsMzUuOTkzIDEzNC4zMDcsNDAuOTMxIEMxMjUuNzkzLDQ1Ljg0NyAxMTkuODUxLDUyLjAwNCAxMTcuMTI0LDU4LjczNiBDMTE1LjI3LDYzLjMxNCAxMTUuNTAxLDY4LjExMiAxMTcuNzksNzIuNjExIEMxMTguMTYsNzMuMzM2IDExNy44NDUsNzQuMTI0IDExNi45OSw3NC42MTcgTDExMy45MDksNzYuMzk3IEMxMTMuODM2LDc2LjQzOCAxMTMuNzU2LDc2LjQ1OSAxMTMuNjc1LDc2LjQ1OSIgaWQ9IkZpbGwtMjQiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTUzLjMxNiwyMS4yNzkgQzE1MC45MDMsMjEuMjc5IDE0OC40OTUsMjAuNzUxIDE0Ni42NjQsMTkuNjkzIEMxNDQuODQ2LDE4LjY0NCAxNDMuODQ0LDE3LjIzMiAxNDMuODQ0LDE1LjcxOCBDMTQzLjg0NCwxNC4xOTEgMTQ0Ljg2LDEyLjc2MyAxNDYuNzA1LDExLjY5OCBMMTU2LjE5OCw2LjA5MSBDMTU2LjMwOSw2LjAyNSAxNTYuNDUyLDYuMDYyIDE1Ni41MTgsNi4xNzMgQzE1Ni41ODMsNi4yODQgMTU2LjU0Nyw2LjQyNyAxNTYuNDM2LDYuNDkzIEwxNDYuOTQsMTIuMTAyIEMxNDUuMjQ0LDEzLjA4MSAxNDQuMzEyLDE0LjM2NSAxNDQuMzEyLDE1LjcxOCBDMTQ0LjMxMiwxNy4wNTggMTQ1LjIzLDE4LjMyNiAxNDYuODk3LDE5LjI4OSBDMTUwLjQ0NiwyMS4zMzggMTU2LjI0LDIxLjMyNyAxNTkuODExLDE5LjI2NSBMMTY5LjU1OSwxMy42MzcgQzE2OS42NywxMy41NzMgMTY5LjgxMywxMy42MTEgMTY5Ljg3OCwxMy43MjMgQzE2OS45NDMsMTMuODM0IDE2OS45MDQsMTMuOTc3IDE2OS43OTMsMTQuMDQyIEwxNjAuMDQ1LDE5LjY3IEMxNTguMTg3LDIwLjc0MiAxNTUuNzQ5LDIxLjI3OSAxNTMuMzE2LDIxLjI3OSIgaWQ9IkZpbGwtMjUiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEzLjY3NSw3NS45OTIgTDY3Ljc2Miw0OS40ODQiIGlkPSJGaWxsLTI2IiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTExMy42NzUsNzYuMzQyIEMxMTMuNjE1LDc2LjM0MiAxMTMuNTU1LDc2LjMyNyAxMTMuNSw3Ni4yOTUgTDY3LjU4Nyw0OS43ODcgQzY3LjQxOSw0OS42OSA2Ny4zNjIsNDkuNDc2IDY3LjQ1OSw0OS4zMDkgQzY3LjU1Niw0OS4xNDEgNjcuNzcsNDkuMDgzIDY3LjkzNyw0OS4xOCBMMTEzLjg1LDc1LjY4OCBDMTE0LjAxOCw3NS43ODUgMTE0LjA3NSw3NiAxMTMuOTc4LDc2LjE2NyBDMTEzLjkxNCw3Ni4yNzkgMTEzLjc5Niw3Ni4zNDIgMTEzLjY3NSw3Ni4zNDIiIGlkPSJGaWxsLTI3IiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTY3Ljc2Miw0OS40ODQgTDY3Ljc2MiwxMDMuNDg1IEM2Ny43NjIsMTA0LjU3NSA2OC41MzIsMTA1LjkwMyA2OS40ODIsMTA2LjQ1MiBMMTExLjk1NSwxMzAuOTczIEMxMTIuOTA1LDEzMS41MjIgMTEzLjY3NSwxMzEuMDgzIDExMy42NzUsMTI5Ljk5MyBMMTEzLjY3NSw3NS45OTIiIGlkPSJGaWxsLTI4IiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTExMi43MjcsMTMxLjU2MSBDMTEyLjQzLDEzMS41NjEgMTEyLjEwNywxMzEuNDY2IDExMS43OCwxMzEuMjc2IEw2OS4zMDcsMTA2Ljc1NSBDNjguMjQ0LDEwNi4xNDIgNjcuNDEyLDEwNC43MDUgNjcuNDEyLDEwMy40ODUgTDY3LjQxMiw0OS40ODQgQzY3LjQxMiw0OS4yOSA2Ny41NjksNDkuMTM0IDY3Ljc2Miw0OS4xMzQgQzY3Ljk1Niw0OS4xMzQgNjguMTEzLDQ5LjI5IDY4LjExMyw0OS40ODQgTDY4LjExMywxMDMuNDg1IEM2OC4xMTMsMTA0LjQ0NSA2OC44MiwxMDUuNjY1IDY5LjY1NywxMDYuMTQ4IEwxMTIuMTMsMTMwLjY3IEMxMTIuNDc0LDEzMC44NjggMTEyLjc5MSwxMzAuOTEzIDExMywxMzAuNzkyIEMxMTMuMjA2LDEzMC42NzMgMTEzLjMyNSwxMzAuMzgxIDExMy4zMjUsMTI5Ljk5MyBMMTEzLjMyNSw3NS45OTIgQzExMy4zMjUsNzUuNzk4IDExMy40ODIsNzUuNjQxIDExMy42NzUsNzUuNjQxIEMxMTMuODY5LDc1LjY0MSAxMTQuMDI1LDc1Ljc5OCAxMTQuMDI1LDc1Ljk5MiBMMTE0LjAyNSwxMjkuOTkzIEMxMTQuMDI1LDEzMC42NDggMTEzLjc4NiwxMzEuMTQ3IDExMy4zNSwxMzEuMzk5IEMxMTMuMTYyLDEzMS41MDcgMTEyLjk1MiwxMzEuNTYxIDExMi43MjcsMTMxLjU2MSIgaWQ9IkZpbGwtMjkiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEyLjg2LDQwLjUxMiBDMTEyLjg2LDQwLjUxMiAxMTIuODYsNDAuNTEyIDExMi44NTksNDAuNTEyIEMxMTAuNTQxLDQwLjUxMiAxMDguMzYsMzkuOTkgMTA2LjcxNywzOS4wNDEgQzEwNS4wMTIsMzguMDU3IDEwNC4wNzQsMzYuNzI2IDEwNC4wNzQsMzUuMjkyIEMxMDQuMDc0LDMzLjg0NyAxMDUuMDI2LDMyLjUwMSAxMDYuNzU0LDMxLjUwNCBMMTE4Ljc5NSwyNC41NTEgQzEyMC40NjMsMjMuNTg5IDEyMi42NjksMjMuMDU4IDEyNS4wMDcsMjMuMDU4IEMxMjcuMzI1LDIzLjA1OCAxMjkuNTA2LDIzLjU4MSAxMzEuMTUsMjQuNTMgQzEzMi44NTQsMjUuNTE0IDEzMy43OTMsMjYuODQ1IDEzMy43OTMsMjguMjc4IEMxMzMuNzkzLDI5LjcyNCAxMzIuODQxLDMxLjA2OSAxMzEuMTEzLDMyLjA2NyBMMTE5LjA3MSwzOS4wMTkgQzExNy40MDMsMzkuOTgyIDExNS4xOTcsNDAuNTEyIDExMi44Niw0MC41MTIgTDExMi44Niw0MC41MTIgWiBNMTI1LjAwNywyMy43NTkgQzEyMi43OSwyMy43NTkgMTIwLjcwOSwyNC4yNTYgMTE5LjE0NiwyNS4xNTggTDEwNy4xMDQsMzIuMTEgQzEwNS42MDIsMzIuOTc4IDEwNC43NzQsMzQuMTA4IDEwNC43NzQsMzUuMjkyIEMxMDQuNzc0LDM2LjQ2NSAxMDUuNTg5LDM3LjU4MSAxMDcuMDY3LDM4LjQzNCBDMTA4LjYwNSwzOS4zMjMgMTEwLjY2MywzOS44MTIgMTEyLjg1OSwzOS44MTIgTDExMi44NiwzOS44MTIgQzExNS4wNzYsMzkuODEyIDExNy4xNTgsMzkuMzE1IDExOC43MjEsMzguNDEzIEwxMzAuNzYyLDMxLjQ2IEMxMzIuMjY0LDMwLjU5MyAxMzMuMDkyLDI5LjQ2MyAxMzMuMDkyLDI4LjI3OCBDMTMzLjA5MiwyNy4xMDYgMTMyLjI3OCwyNS45OSAxMzAuOCwyNS4xMzYgQzEyOS4yNjEsMjQuMjQ4IDEyNy4yMDQsMjMuNzU5IDEyNS4wMDcsMjMuNzU5IEwxMjUuMDA3LDIzLjc1OSBaIiBpZD0iRmlsbC0zMCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNjUuNjMsMTYuMjE5IEwxNTkuODk2LDE5LjUzIEMxNTYuNzI5LDIxLjM1OCAxNTEuNjEsMjEuMzY3IDE0OC40NjMsMTkuNTUgQzE0NS4zMTYsMTcuNzMzIDE0NS4zMzIsMTQuNzc4IDE0OC40OTksMTIuOTQ5IEwxNTQuMjMzLDkuNjM5IEwxNjUuNjMsMTYuMjE5IiBpZD0iRmlsbC0zMSIgZmlsbD0iI0ZBRkFGQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNTQuMjMzLDEwLjQ0OCBMMTY0LjIyOCwxNi4yMTkgTDE1OS41NDYsMTguOTIzIEMxNTguMTEyLDE5Ljc1IDE1Ni4xOTQsMjAuMjA2IDE1NC4xNDcsMjAuMjA2IEMxNTIuMTE4LDIwLjIwNiAxNTAuMjI0LDE5Ljc1NyAxNDguODE0LDE4Ljk0MyBDMTQ3LjUyNCwxOC4xOTkgMTQ2LjgxNCwxNy4yNDkgMTQ2LjgxNCwxNi4yNjkgQzE0Ni44MTQsMTUuMjc4IDE0Ny41MzcsMTQuMzE0IDE0OC44NSwxMy41NTYgTDE1NC4yMzMsMTAuNDQ4IE0xNTQuMjMzLDkuNjM5IEwxNDguNDk5LDEyLjk0OSBDMTQ1LjMzMiwxNC43NzggMTQ1LjMxNiwxNy43MzMgMTQ4LjQ2MywxOS41NSBDMTUwLjAzMSwyMC40NTUgMTUyLjA4NiwyMC45MDcgMTU0LjE0NywyMC45MDcgQzE1Ni4yMjQsMjAuOTA3IDE1OC4zMDYsMjAuNDQ3IDE1OS44OTYsMTkuNTMgTDE2NS42MywxNi4yMTkgTDE1NC4yMzMsOS42MzkiIGlkPSJGaWxsLTMyIiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE0NS40NDUsNzIuNjY3IEwxNDUuNDQ1LDcyLjY2NyBDMTQzLjY3Miw3Mi42NjcgMTQyLjIwNCw3MS44MTcgMTQxLjIwMiw3MC40MjIgQzE0MS4xMzUsNzAuMzMgMTQxLjE0NSw3MC4xNDcgMTQxLjIyNSw3MC4wNjYgQzE0MS4zMDUsNjkuOTg1IDE0MS40MzIsNjkuOTQ2IDE0MS41MjUsNzAuMDExIEMxNDIuMzA2LDcwLjU1OSAxNDMuMjMxLDcwLjgyMyAxNDQuMjc2LDcwLjgyMiBDMTQ1LjU5OCw3MC44MjIgMTQ3LjAzLDcwLjM3NiAxNDguNTMyLDY5LjUwOSBDMTUzLjg0Miw2Ni40NDMgMTU4LjE2Myw1OC45ODcgMTU4LjE2Myw1Mi44OTQgQzE1OC4xNjMsNTAuOTY3IDE1Ny43MjEsNDkuMzMyIDE1Ni44ODQsNDguMTY4IEMxNTYuODE4LDQ4LjA3NiAxNTYuODI4LDQ3Ljk0OCAxNTYuOTA4LDQ3Ljg2NyBDMTU2Ljk4OCw0Ny43ODYgMTU3LjExNCw0Ny43NzQgMTU3LjIwOCw0Ny44NCBDMTU4Ljg3OCw0OS4wMTIgMTU5Ljc5OCw1MS4yMiAxNTkuNzk4LDU0LjA1OSBDMTU5Ljc5OCw2MC4zMDEgMTU1LjM3Myw2OC4wNDYgMTQ5LjkzMyw3MS4xODYgQzE0OC4zNiw3Mi4wOTQgMTQ2Ljg1LDcyLjY2NyAxNDUuNDQ1LDcyLjY2NyBMMTQ1LjQ0NSw3Mi42NjcgWiBNMTQyLjQ3Niw3MSBDMTQzLjI5LDcxLjY1MSAxNDQuMjk2LDcyLjAwMiAxNDUuNDQ1LDcyLjAwMiBDMTQ2Ljc2Nyw3Mi4wMDIgMTQ4LjE5OCw3MS41NSAxNDkuNyw3MC42ODIgQzE1NS4wMSw2Ny42MTcgMTU5LjMzMSw2MC4xNTkgMTU5LjMzMSw1NC4wNjUgQzE1OS4zMzEsNTIuMDg1IDE1OC44NjgsNTAuNDM1IDE1OC4wMDYsNDkuMjcyIEMxNTguNDE3LDUwLjMwNyAxNTguNjMsNTEuNTMyIDE1OC42Myw1Mi44OTIgQzE1OC42Myw1OS4xMzQgMTU0LjIwNSw2Ni43NjcgMTQ4Ljc2NSw2OS45MDcgQzE0Ny4xOTIsNzAuODE2IDE0NS42ODEsNzEuMjgzIDE0NC4yNzYsNzEuMjgzIEMxNDMuNjM0LDcxLjI4MyAxNDMuMDMzLDcxLjE5MiAxNDIuNDc2LDcxIEwxNDIuNDc2LDcxIFoiIGlkPSJGaWxsLTMzIiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE0OC42NDgsNjkuNzA0IEMxNTQuMDMyLDY2LjU5NiAxNTguMzk2LDU5LjA2OCAxNTguMzk2LDUyLjg5MSBDMTU4LjM5Niw1MC44MzkgMTU3LjkxMyw0OS4xOTggMTU3LjA3NCw0OC4wMyBDMTU1LjI4OSw0Ni43NzggMTUyLjY5OSw0Ni44MzYgMTQ5LjgxNiw0OC41MDEgQzE0NC40MzMsNTEuNjA5IDE0MC4wNjgsNTkuMTM3IDE0MC4wNjgsNjUuMzE0IEMxNDAuMDY4LDY3LjM2NSAxNDAuNTUyLDY5LjAwNiAxNDEuMzkxLDcwLjE3NCBDMTQzLjE3Niw3MS40MjcgMTQ1Ljc2NSw3MS4zNjkgMTQ4LjY0OCw2OS43MDQiIGlkPSJGaWxsLTM0IiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE0NC4yNzYsNzEuMjc2IEwxNDQuMjc2LDcxLjI3NiBDMTQzLjEzMyw3MS4yNzYgMTQyLjExOCw3MC45NjkgMTQxLjI1Nyw3MC4zNjUgQzE0MS4yMzYsNzAuMzUxIDE0MS4yMTcsNzAuMzMyIDE0MS4yMDIsNzAuMzExIEMxNDAuMzA3LDY5LjA2NyAxMzkuODM1LDY3LjMzOSAxMzkuODM1LDY1LjMxNCBDMTM5LjgzNSw1OS4wNzMgMTQ0LjI2LDUxLjQzOSAxNDkuNyw0OC4yOTggQzE1MS4yNzMsNDcuMzkgMTUyLjc4NCw0Ni45MjkgMTU0LjE4OSw0Ni45MjkgQzE1NS4zMzIsNDYuOTI5IDE1Ni4zNDcsNDcuMjM2IDE1Ny4yMDgsNDcuODM5IEMxNTcuMjI5LDQ3Ljg1NCAxNTcuMjQ4LDQ3Ljg3MyAxNTcuMjYzLDQ3Ljg5NCBDMTU4LjE1Nyw0OS4xMzggMTU4LjYzLDUwLjg2NSAxNTguNjMsNTIuODkxIEMxNTguNjMsNTkuMTMyIDE1NC4yMDUsNjYuNzY2IDE0OC43NjUsNjkuOTA3IEMxNDcuMTkyLDcwLjgxNSAxNDUuNjgxLDcxLjI3NiAxNDQuMjc2LDcxLjI3NiBMMTQ0LjI3Niw3MS4yNzYgWiBNMTQxLjU1OCw3MC4xMDQgQzE0Mi4zMzEsNzAuNjM3IDE0My4yNDUsNzEuMDA1IDE0NC4yNzYsNzEuMDA1IEMxNDUuNTk4LDcxLjAwNSAxNDcuMDMsNzAuNDY3IDE0OC41MzIsNjkuNiBDMTUzLjg0Miw2Ni41MzQgMTU4LjE2Myw1OS4wMzMgMTU4LjE2Myw1Mi45MzkgQzE1OC4xNjMsNTEuMDMxIDE1Ny43MjksNDkuMzg1IDE1Ni45MDcsNDguMjIzIEMxNTYuMTMzLDQ3LjY5MSAxNTUuMjE5LDQ3LjQwOSAxNTQuMTg5LDQ3LjQwOSBDMTUyLjg2Nyw0Ny40MDkgMTUxLjQzNSw0Ny44NDIgMTQ5LjkzMyw0OC43MDkgQzE0NC42MjMsNTEuNzc1IDE0MC4zMDIsNTkuMjczIDE0MC4zMDIsNjUuMzY2IEMxNDAuMzAyLDY3LjI3NiAxNDAuNzM2LDY4Ljk0MiAxNDEuNTU4LDcwLjEwNCBMMTQxLjU1OCw3MC4xMDQgWiIgaWQ9IkZpbGwtMzUiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTUwLjcyLDY1LjM2MSBMMTUwLjM1Nyw2NS4wNjYgQzE1MS4xNDcsNjQuMDkyIDE1MS44NjksNjMuMDQgMTUyLjUwNSw2MS45MzggQzE1My4zMTMsNjAuNTM5IDE1My45NzgsNTkuMDY3IDE1NC40ODIsNTcuNTYzIEwxNTQuOTI1LDU3LjcxMiBDMTU0LjQxMiw1OS4yNDUgMTUzLjczMyw2MC43NDUgMTUyLjkxLDYyLjE3MiBDMTUyLjI2Miw2My4yOTUgMTUxLjUyNSw2NC4zNjggMTUwLjcyLDY1LjM2MSIgaWQ9IkZpbGwtMzYiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTE1LjkxNyw4NC41MTQgTDExNS41NTQsODQuMjIgQzExNi4zNDQsODMuMjQ1IDExNy4wNjYsODIuMTk0IDExNy43MDIsODEuMDkyIEMxMTguNTEsNzkuNjkyIDExOS4xNzUsNzguMjIgMTE5LjY3OCw3Ni43MTcgTDEyMC4xMjEsNzYuODY1IEMxMTkuNjA4LDc4LjM5OCAxMTguOTMsNzkuODk5IDExOC4xMDYsODEuMzI2IEMxMTcuNDU4LDgyLjQ0OCAxMTYuNzIyLDgzLjUyMSAxMTUuOTE3LDg0LjUxNCIgaWQ9IkZpbGwtMzciIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTE0LDEzMC40NzYgTDExNCwxMzAuMDA4IEwxMTQsNzYuMDUyIEwxMTQsNzUuNTg0IEwxMTQsNzYuMDUyIEwxMTQsMTMwLjAwOCBMMTE0LDEzMC40NzYiIGlkPSJGaWxsLTM4IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8ZyBpZD0iSW1wb3J0ZWQtTGF5ZXJzLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDYyLjAwMDAwMCwgMC4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTkuODIyLDM3LjQ3NCBDMTkuODM5LDM3LjMzOSAxOS43NDcsMzcuMTk0IDE5LjU1NSwzNy4wODIgQzE5LjIyOCwzNi44OTQgMTguNzI5LDM2Ljg3MiAxOC40NDYsMzcuMDM3IEwxMi40MzQsNDAuNTA4IEMxMi4zMDMsNDAuNTg0IDEyLjI0LDQwLjY4NiAxMi4yNDMsNDAuNzkzIEMxMi4yNDUsNDAuOTI1IDEyLjI0NSw0MS4yNTQgMTIuMjQ1LDQxLjM3MSBMMTIuMjQ1LDQxLjQxNCBMMTIuMjM4LDQxLjU0MiBDOC4xNDgsNDMuODg3IDUuNjQ3LDQ1LjMyMSA1LjY0Nyw0NS4zMjEgQzUuNjQ2LDQ1LjMyMSAzLjU3LDQ2LjM2NyAyLjg2LDUwLjUxMyBDMi44Niw1MC41MTMgMS45NDgsNTcuNDc0IDEuOTYyLDcwLjI1OCBDMS45NzcsODIuODI4IDIuNTY4LDg3LjMyOCAzLjEyOSw5MS42MDkgQzMuMzQ5LDkzLjI5MyA2LjEzLDkzLjczNCA2LjEzLDkzLjczNCBDNi40NjEsOTMuNzc0IDYuODI4LDkzLjcwNyA3LjIxLDkzLjQ4NiBMODIuNDgzLDQ5LjkzNSBDODQuMjkxLDQ4Ljg2NiA4NS4xNSw0Ni4yMTYgODUuNTM5LDQzLjY1MSBDODYuNzUyLDM1LjY2MSA4Ny4yMTQsMTAuNjczIDg1LjI2NCwzLjc3MyBDODUuMDY4LDMuMDggODQuNzU0LDIuNjkgODQuMzk2LDIuNDkxIEw4Mi4zMSwxLjcwMSBDODEuNTgzLDEuNzI5IDgwLjg5NCwyLjE2OCA4MC43NzYsMi4yMzYgQzgwLjYzNiwyLjMxNyA0MS44MDcsMjQuNTg1IDIwLjAzMiwzNy4wNzIgTDE5LjgyMiwzNy40NzQiIGlkPSJGaWxsLTEiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNODIuMzExLDEuNzAxIEw4NC4zOTYsMi40OTEgQzg0Ljc1NCwyLjY5IDg1LjA2OCwzLjA4IDg1LjI2NCwzLjc3MyBDODcuMjEzLDEwLjY3MyA4Ni43NTEsMzUuNjYgODUuNTM5LDQzLjY1MSBDODUuMTQ5LDQ2LjIxNiA4NC4yOSw0OC44NjYgODIuNDgzLDQ5LjkzNSBMNy4yMSw5My40ODYgQzYuODk3LDkzLjY2NyA2LjU5NSw5My43NDQgNi4zMTQsOTMuNzQ0IEw2LjEzMSw5My43MzMgQzYuMTMxLDkzLjczNCAzLjM0OSw5My4yOTMgMy4xMjgsOTEuNjA5IEMyLjU2OCw4Ny4zMjcgMS45NzcsODIuODI4IDEuOTYzLDcwLjI1OCBDMS45NDgsNTcuNDc0IDIuODYsNTAuNTEzIDIuODYsNTAuNTEzIEMzLjU3LDQ2LjM2NyA1LjY0Nyw0NS4zMjEgNS42NDcsNDUuMzIxIEM1LjY0Nyw0NS4zMjEgOC4xNDgsNDMuODg3IDEyLjIzOCw0MS41NDIgTDEyLjI0NSw0MS40MTQgTDEyLjI0NSw0MS4zNzEgQzEyLjI0NSw0MS4yNTQgMTIuMjQ1LDQwLjkyNSAxMi4yNDMsNDAuNzkzIEMxMi4yNCw0MC42ODYgMTIuMzAyLDQwLjU4MyAxMi40MzQsNDAuNTA4IEwxOC40NDYsMzcuMDM2IEMxOC41NzQsMzYuOTYyIDE4Ljc0NiwzNi45MjYgMTguOTI3LDM2LjkyNiBDMTkuMTQ1LDM2LjkyNiAxOS4zNzYsMzYuOTc5IDE5LjU1NCwzNy4wODIgQzE5Ljc0NywzNy4xOTQgMTkuODM5LDM3LjM0IDE5LjgyMiwzNy40NzQgTDIwLjAzMywzNy4wNzIgQzQxLjgwNiwyNC41ODUgODAuNjM2LDIuMzE4IDgwLjc3NywyLjIzNiBDODAuODk0LDIuMTY4IDgxLjU4MywxLjcyOSA4Mi4zMTEsMS43MDEgTTgyLjMxMSwwLjcwNCBMODIuMjcyLDAuNzA1IEM4MS42NTQsMC43MjggODAuOTg5LDAuOTQ5IDgwLjI5OCwxLjM2MSBMODAuMjc3LDEuMzczIEM4MC4xMjksMS40NTggNTkuNzY4LDEzLjEzNSAxOS43NTgsMzYuMDc5IEMxOS41LDM1Ljk4MSAxOS4yMTQsMzUuOTI5IDE4LjkyNywzNS45MjkgQzE4LjU2MiwzNS45MjkgMTguMjIzLDM2LjAxMyAxNy45NDcsMzYuMTczIEwxMS45MzUsMzkuNjQ0IEMxMS40OTMsMzkuODk5IDExLjIzNiw0MC4zMzQgMTEuMjQ2LDQwLjgxIEwxMS4yNDcsNDAuOTYgTDUuMTY3LDQ0LjQ0NyBDNC43OTQsNDQuNjQ2IDIuNjI1LDQ1Ljk3OCAxLjg3Nyw1MC4zNDUgTDEuODcxLDUwLjM4NCBDMS44NjIsNTAuNDU0IDAuOTUxLDU3LjU1NyAwLjk2NSw3MC4yNTkgQzAuOTc5LDgyLjg3OSAxLjU2OCw4Ny4zNzUgMi4xMzcsOTEuNzI0IEwyLjEzOSw5MS43MzkgQzIuNDQ3LDk0LjA5NCA1LjYxNCw5NC42NjIgNS45NzUsOTQuNzE5IEw2LjAwOSw5NC43MjMgQzYuMTEsOTQuNzM2IDYuMjEzLDk0Ljc0MiA2LjMxNCw5NC43NDIgQzYuNzksOTQuNzQyIDcuMjYsOTQuNjEgNy43MSw5NC4zNSBMODIuOTgzLDUwLjc5OCBDODQuNzk0LDQ5LjcyNyA4NS45ODIsNDcuMzc1IDg2LjUyNSw0My44MDEgQzg3LjcxMSwzNS45ODcgODguMjU5LDEwLjcwNSA4Ni4yMjQsMy41MDIgQzg1Ljk3MSwyLjYwOSA4NS41MiwxLjk3NSA4NC44ODEsMS42MiBMODQuNzQ5LDEuNTU4IEw4Mi42NjQsMC43NjkgQzgyLjU1MSwwLjcyNSA4Mi40MzEsMC43MDQgODIuMzExLDAuNzA0IiBpZD0iRmlsbC0yIiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTY2LjI2NywxMS41NjUgTDY3Ljc2MiwxMS45OTkgTDExLjQyMyw0NC4zMjUiIGlkPSJGaWxsLTMiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTIuMjAyLDkwLjU0NSBDMTIuMDI5LDkwLjU0NSAxMS44NjIsOTAuNDU1IDExLjc2OSw5MC4yOTUgQzExLjYzMiw5MC4wNTcgMTEuNzEzLDg5Ljc1MiAxMS45NTIsODkuNjE0IEwzMC4zODksNzguOTY5IEMzMC42MjgsNzguODMxIDMwLjkzMyw3OC45MTMgMzEuMDcxLDc5LjE1MiBDMzEuMjA4LDc5LjM5IDMxLjEyNyw3OS42OTYgMzAuODg4LDc5LjgzMyBMMTIuNDUxLDkwLjQ3OCBMMTIuMjAyLDkwLjU0NSIgaWQ9IkZpbGwtNCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMy43NjQsNDIuNjU0IEwxMy42NTYsNDIuNTkyIEwxMy43MDIsNDIuNDIxIEwxOC44MzcsMzkuNDU3IEwxOS4wMDcsMzkuNTAyIEwxOC45NjIsMzkuNjczIEwxMy44MjcsNDIuNjM3IEwxMy43NjQsNDIuNjU0IiBpZD0iRmlsbC01IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTguNTIsOTAuMzc1IEw4LjUyLDQ2LjQyMSBMOC41ODMsNDYuMzg1IEw3NS44NCw3LjU1NCBMNzUuODQsNTEuNTA4IEw3NS43NzgsNTEuNTQ0IEw4LjUyLDkwLjM3NSBMOC41Miw5MC4zNzUgWiBNOC43Nyw0Ni41NjQgTDguNzcsODkuOTQ0IEw3NS41OTEsNTEuMzY1IEw3NS41OTEsNy45ODUgTDguNzcsNDYuNTY0IEw4Ljc3LDQ2LjU2NCBaIiBpZD0iRmlsbC02IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTI0Ljk4Niw4My4xODIgQzI0Ljc1Niw4My4zMzEgMjQuMzc0LDgzLjU2NiAyNC4xMzcsODMuNzA1IEwxMi42MzIsOTAuNDA2IEMxMi4zOTUsOTAuNTQ1IDEyLjQyNiw5MC42NTggMTIuNyw5MC42NTggTDEzLjI2NSw5MC42NTggQzEzLjU0LDkwLjY1OCAxMy45NTgsOTAuNTQ1IDE0LjE5NSw5MC40MDYgTDI1LjcsODMuNzA1IEMyNS45MzcsODMuNTY2IDI2LjEyOCw4My40NTIgMjYuMTI1LDgzLjQ0OSBDMjYuMTIyLDgzLjQ0NyAyNi4xMTksODMuMjIgMjYuMTE5LDgyLjk0NiBDMjYuMTE5LDgyLjY3MiAyNS45MzEsODIuNTY5IDI1LjcwMSw4Mi43MTkgTDI0Ljk4Niw4My4xODIiIGlkPSJGaWxsLTciIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTMuMjY2LDkwLjc4MiBMMTIuNyw5MC43ODIgQzEyLjUsOTAuNzgyIDEyLjM4NCw5MC43MjYgMTIuMzU0LDkwLjYxNiBDMTIuMzI0LDkwLjUwNiAxMi4zOTcsOTAuMzk5IDEyLjU2OSw5MC4yOTkgTDI0LjA3NCw4My41OTcgQzI0LjMxLDgzLjQ1OSAyNC42ODksODMuMjI2IDI0LjkxOCw4My4wNzggTDI1LjYzMyw4Mi42MTQgQzI1LjcyMyw4Mi41NTUgMjUuODEzLDgyLjUyNSAyNS44OTksODIuNTI1IEMyNi4wNzEsODIuNTI1IDI2LjI0NCw4Mi42NTUgMjYuMjQ0LDgyLjk0NiBDMjYuMjQ0LDgzLjE2IDI2LjI0NSw4My4zMDkgMjYuMjQ3LDgzLjM4MyBMMjYuMjUzLDgzLjM4NyBMMjYuMjQ5LDgzLjQ1NiBDMjYuMjQ2LDgzLjUzMSAyNi4yNDYsODMuNTMxIDI1Ljc2Myw4My44MTIgTDE0LjI1OCw5MC41MTQgQzE0LDkwLjY2NSAxMy41NjQsOTAuNzgyIDEzLjI2Niw5MC43ODIgTDEzLjI2Niw5MC43ODIgWiBNMTIuNjY2LDkwLjUzMiBMMTIuNyw5MC41MzMgTDEzLjI2Niw5MC41MzMgQzEzLjUxOCw5MC41MzMgMTMuOTE1LDkwLjQyNSAxNC4xMzIsOTAuMjk5IEwyNS42MzcsODMuNTk3IEMyNS44MDUsODMuNDk5IDI1LjkzMSw4My40MjQgMjUuOTk4LDgzLjM4MyBDMjUuOTk0LDgzLjI5OSAyNS45OTQsODMuMTY1IDI1Ljk5NCw4Mi45NDYgTDI1Ljg5OSw4Mi43NzUgTDI1Ljc2OCw4Mi44MjQgTDI1LjA1NCw4My4yODcgQzI0LjgyMiw4My40MzcgMjQuNDM4LDgzLjY3MyAyNC4yLDgzLjgxMiBMMTIuNjk1LDkwLjUxNCBMMTIuNjY2LDkwLjUzMiBMMTIuNjY2LDkwLjUzMiBaIiBpZD0iRmlsbC04IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTEzLjI2Niw4OS44NzEgTDEyLjcsODkuODcxIEMxMi41LDg5Ljg3MSAxMi4zODQsODkuODE1IDEyLjM1NCw4OS43MDUgQzEyLjMyNCw4OS41OTUgMTIuMzk3LDg5LjQ4OCAxMi41NjksODkuMzg4IEwyNC4wNzQsODIuNjg2IEMyNC4zMzIsODIuNTM1IDI0Ljc2OCw4Mi40MTggMjUuMDY3LDgyLjQxOCBMMjUuNjMyLDgyLjQxOCBDMjUuODMyLDgyLjQxOCAyNS45NDgsODIuNDc0IDI1Ljk3OCw4Mi41ODQgQzI2LjAwOCw4Mi42OTQgMjUuOTM1LDgyLjgwMSAyNS43NjMsODIuOTAxIEwxNC4yNTgsODkuNjAzIEMxNCw4OS43NTQgMTMuNTY0LDg5Ljg3MSAxMy4yNjYsODkuODcxIEwxMy4yNjYsODkuODcxIFogTTEyLjY2Niw4OS42MjEgTDEyLjcsODkuNjIyIEwxMy4yNjYsODkuNjIyIEMxMy41MTgsODkuNjIyIDEzLjkxNSw4OS41MTUgMTQuMTMyLDg5LjM4OCBMMjUuNjM3LDgyLjY4NiBMMjUuNjY3LDgyLjY2OCBMMjUuNjMyLDgyLjY2NyBMMjUuMDY3LDgyLjY2NyBDMjQuODE1LDgyLjY2NyAyNC40MTgsODIuNzc1IDI0LjIsODIuOTAxIEwxMi42OTUsODkuNjAzIEwxMi42NjYsODkuNjIxIEwxMi42NjYsODkuNjIxIFoiIGlkPSJGaWxsLTkiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTIuMzcsOTAuODAxIEwxMi4zNyw4OS41NTQgTDEyLjM3LDkwLjgwMSIgaWQ9IkZpbGwtMTAiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNi4xMyw5My45MDEgQzUuMzc5LDkzLjgwOCA0LjgxNiw5My4xNjQgNC42OTEsOTIuNTI1IEMzLjg2LDg4LjI4NyAzLjU0LDgzLjc0MyAzLjUyNiw3MS4xNzMgQzMuNTExLDU4LjM4OSA0LjQyMyw1MS40MjggNC40MjMsNTEuNDI4IEM1LjEzNCw0Ny4yODIgNy4yMSw0Ni4yMzYgNy4yMSw0Ni4yMzYgQzcuMjEsNDYuMjM2IDgxLjY2NywzLjI1IDgyLjA2OSwzLjAxNyBDODIuMjkyLDIuODg4IDg0LjU1NiwxLjQzMyA4NS4yNjQsMy45NCBDODcuMjE0LDEwLjg0IDg2Ljc1MiwzNS44MjcgODUuNTM5LDQzLjgxOCBDODUuMTUsNDYuMzgzIDg0LjI5MSw0OS4wMzMgODIuNDgzLDUwLjEwMSBMNy4yMSw5My42NTMgQzYuODI4LDkzLjg3NCA2LjQ2MSw5My45NDEgNi4xMyw5My45MDEgQzYuMTMsOTMuOTAxIDMuMzQ5LDkzLjQ2IDMuMTI5LDkxLjc3NiBDMi41NjgsODcuNDk1IDEuOTc3LDgyLjk5NSAxLjk2Miw3MC40MjUgQzEuOTQ4LDU3LjY0MSAyLjg2LDUwLjY4IDIuODYsNTAuNjggQzMuNTcsNDYuNTM0IDUuNjQ3LDQ1LjQ4OSA1LjY0Nyw0NS40ODkgQzUuNjQ2LDQ1LjQ4OSA4LjA2NSw0NC4wOTIgMTIuMjQ1LDQxLjY3OSBMMTMuMTE2LDQxLjU2IEwxOS43MTUsMzcuNzMgTDE5Ljc2MSwzNy4yNjkgTDYuMTMsOTMuOTAxIiBpZD0iRmlsbC0xMSIgZmlsbD0iI0ZBRkFGQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02LjMxNyw5NC4xNjEgTDYuMTAyLDk0LjE0OCBMNi4xMDEsOTQuMTQ4IEw1Ljg1Nyw5NC4xMDEgQzUuMTM4LDkzLjk0NSAzLjA4NSw5My4zNjUgMi44ODEsOTEuODA5IEMyLjMxMyw4Ny40NjkgMS43MjcsODIuOTk2IDEuNzEzLDcwLjQyNSBDMS42OTksNTcuNzcxIDIuNjA0LDUwLjcxOCAyLjYxMyw1MC42NDggQzMuMzM4LDQ2LjQxNyA1LjQ0NSw0NS4zMSA1LjUzNSw0NS4yNjYgTDEyLjE2Myw0MS40MzkgTDEzLjAzMyw0MS4zMiBMMTkuNDc5LDM3LjU3OCBMMTkuNTEzLDM3LjI0NCBDMTkuNTI2LDM3LjEwNyAxOS42NDcsMzcuMDA4IDE5Ljc4NiwzNy4wMjEgQzE5LjkyMiwzNy4wMzQgMjAuMDIzLDM3LjE1NiAyMC4wMDksMzcuMjkzIEwxOS45NSwzNy44ODIgTDEzLjE5OCw0MS44MDEgTDEyLjMyOCw0MS45MTkgTDUuNzcyLDQ1LjcwNCBDNS43NDEsNDUuNzIgMy43ODIsNDYuNzcyIDMuMTA2LDUwLjcyMiBDMy4wOTksNTAuNzgyIDIuMTk4LDU3LjgwOCAyLjIxMiw3MC40MjQgQzIuMjI2LDgyLjk2MyAyLjgwOSw4Ny40MiAzLjM3Myw5MS43MjkgQzMuNDY0LDkyLjQyIDQuMDYyLDkyLjg4MyA0LjY4Miw5My4xODEgQzQuNTY2LDkyLjk4NCA0LjQ4Niw5Mi43NzYgNC40NDYsOTIuNTcyIEMzLjY2NSw4OC41ODggMy4yOTEsODQuMzcgMy4yNzYsNzEuMTczIEMzLjI2Miw1OC41MiA0LjE2Nyw1MS40NjYgNC4xNzYsNTEuMzk2IEM0LjkwMSw0Ny4xNjUgNy4wMDgsNDYuMDU5IDcuMDk4LDQ2LjAxNCBDNy4wOTQsNDYuMDE1IDgxLjU0MiwzLjAzNCA4MS45NDQsMi44MDIgTDgxLjk3MiwyLjc4NSBDODIuODc2LDIuMjQ3IDgzLjY5MiwyLjA5NyA4NC4zMzIsMi4zNTIgQzg0Ljg4NywyLjU3MyA4NS4yODEsMy4wODUgODUuNTA0LDMuODcyIEM4Ny41MTgsMTEgODYuOTY0LDM2LjA5MSA4NS43ODUsNDMuODU1IEM4NS4yNzgsNDcuMTk2IDg0LjIxLDQ5LjM3IDgyLjYxLDUwLjMxNyBMNy4zMzUsOTMuODY5IEM2Ljk5OSw5NC4wNjMgNi42NTgsOTQuMTYxIDYuMzE3LDk0LjE2MSBMNi4zMTcsOTQuMTYxIFogTTYuMTcsOTMuNjU0IEM2LjQ2Myw5My42OSA2Ljc3NCw5My42MTcgNy4wODUsOTMuNDM3IEw4Mi4zNTgsNDkuODg2IEM4NC4xODEsNDguODA4IDg0Ljk2LDQ1Ljk3MSA4NS4yOTIsNDMuNzggQzg2LjQ2NiwzNi4wNDkgODcuMDIzLDExLjA4NSA4NS4wMjQsNC4wMDggQzg0Ljg0NiwzLjM3NyA4NC41NTEsMi45NzYgODQuMTQ4LDIuODE2IEM4My42NjQsMi42MjMgODIuOTgyLDIuNzY0IDgyLjIyNywzLjIxMyBMODIuMTkzLDMuMjM0IEM4MS43OTEsMy40NjYgNy4zMzUsNDYuNDUyIDcuMzM1LDQ2LjQ1MiBDNy4zMDQsNDYuNDY5IDUuMzQ2LDQ3LjUyMSA0LjY2OSw1MS40NzEgQzQuNjYyLDUxLjUzIDMuNzYxLDU4LjU1NiAzLjc3NSw3MS4xNzMgQzMuNzksODQuMzI4IDQuMTYxLDg4LjUyNCA0LjkzNiw5Mi40NzYgQzUuMDI2LDkyLjkzNyA1LjQxMiw5My40NTkgNS45NzMsOTMuNjE1IEM2LjA4Nyw5My42NCA2LjE1OCw5My42NTIgNi4xNjksOTMuNjU0IEw2LjE3LDkzLjY1NCBMNi4xNyw5My42NTQgWiIgaWQ9IkZpbGwtMTIiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNy4zMTcsNjguOTgyIEM3LjgwNiw2OC43MDEgOC4yMDIsNjguOTI2IDguMjAyLDY5LjQ4NyBDOC4yMDIsNzAuMDQ3IDcuODA2LDcwLjczIDcuMzE3LDcxLjAxMiBDNi44MjksNzEuMjk0IDYuNDMzLDcxLjA2OSA2LjQzMyw3MC41MDggQzYuNDMzLDY5Ljk0OCA2LjgyOSw2OS4yNjUgNy4zMTcsNjguOTgyIiBpZD0iRmlsbC0xMyIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02LjkyLDcxLjEzMyBDNi42MzEsNzEuMTMzIDYuNDMzLDcwLjkwNSA2LjQzMyw3MC41MDggQzYuNDMzLDY5Ljk0OCA2LjgyOSw2OS4yNjUgNy4zMTcsNjguOTgyIEM3LjQ2LDY4LjkgNy41OTUsNjguODYxIDcuNzE0LDY4Ljg2MSBDOC4wMDMsNjguODYxIDguMjAyLDY5LjA5IDguMjAyLDY5LjQ4NyBDOC4yMDIsNzAuMDQ3IDcuODA2LDcwLjczIDcuMzE3LDcxLjAxMiBDNy4xNzQsNzEuMDk0IDcuMDM5LDcxLjEzMyA2LjkyLDcxLjEzMyBNNy43MTQsNjguNjc0IEM3LjU1Nyw2OC42NzQgNy4zOTIsNjguNzIzIDcuMjI0LDY4LjgyMSBDNi42NzYsNjkuMTM4IDYuMjQ2LDY5Ljg3OSA2LjI0Niw3MC41MDggQzYuMjQ2LDcwLjk5NCA2LjUxNyw3MS4zMiA2LjkyLDcxLjMyIEM3LjA3OCw3MS4zMiA3LjI0Myw3MS4yNzEgNy40MTEsNzEuMTc0IEM3Ljk1OSw3MC44NTcgOC4zODksNzAuMTE3IDguMzg5LDY5LjQ4NyBDOC4zODksNjkuMDAxIDguMTE3LDY4LjY3NCA3LjcxNCw2OC42NzQiIGlkPSJGaWxsLTE0IiBmaWxsPSIjODA5N0EyIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTYuOTIsNzAuOTQ3IEM2LjY0OSw3MC45NDcgNi42MjEsNzAuNjQgNi42MjEsNzAuNTA4IEM2LjYyMSw3MC4wMTcgNi45ODIsNjkuMzkyIDcuNDExLDY5LjE0NSBDNy41MjEsNjkuMDgyIDcuNjI1LDY5LjA0OSA3LjcxNCw2OS4wNDkgQzcuOTg2LDY5LjA0OSA4LjAxNSw2OS4zNTUgOC4wMTUsNjkuNDg3IEM4LjAxNSw2OS45NzggNy42NTIsNzAuNjAzIDcuMjI0LDcwLjg1MSBDNy4xMTUsNzAuOTE0IDcuMDEsNzAuOTQ3IDYuOTIsNzAuOTQ3IE03LjcxNCw2OC44NjEgQzcuNTk1LDY4Ljg2MSA3LjQ2LDY4LjkgNy4zMTcsNjguOTgyIEM2LjgyOSw2OS4yNjUgNi40MzMsNjkuOTQ4IDYuNDMzLDcwLjUwOCBDNi40MzMsNzAuOTA1IDYuNjMxLDcxLjEzMyA2LjkyLDcxLjEzMyBDNy4wMzksNzEuMTMzIDcuMTc0LDcxLjA5NCA3LjMxNyw3MS4wMTIgQzcuODA2LDcwLjczIDguMjAyLDcwLjA0NyA4LjIwMiw2OS40ODcgQzguMjAyLDY5LjA5IDguMDAzLDY4Ljg2MSA3LjcxNCw2OC44NjEiIGlkPSJGaWxsLTE1IiBmaWxsPSIjODA5N0EyIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTcuNDQ0LDg1LjM1IEM3LjcwOCw4NS4xOTggNy45MjEsODUuMzE5IDcuOTIxLDg1LjYyMiBDNy45MjEsODUuOTI1IDcuNzA4LDg2LjI5MiA3LjQ0NCw4Ni40NDQgQzcuMTgxLDg2LjU5NyA2Ljk2Nyw4Ni40NzUgNi45NjcsODYuMTczIEM2Ljk2Nyw4NS44NzEgNy4xODEsODUuNTAyIDcuNDQ0LDg1LjM1IiBpZD0iRmlsbC0xNiIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik03LjIzLDg2LjUxIEM3LjA3NCw4Ni41MSA2Ljk2Nyw4Ni4zODcgNi45NjcsODYuMTczIEM2Ljk2Nyw4NS44NzEgNy4xODEsODUuNTAyIDcuNDQ0LDg1LjM1IEM3LjUyMSw4NS4zMDUgNy41OTQsODUuMjg0IDcuNjU4LDg1LjI4NCBDNy44MTQsODUuMjg0IDcuOTIxLDg1LjQwOCA3LjkyMSw4NS42MjIgQzcuOTIxLDg1LjkyNSA3LjcwOCw4Ni4yOTIgNy40NDQsODYuNDQ0IEM3LjM2Nyw4Ni40ODkgNy4yOTQsODYuNTEgNy4yMyw4Ni41MSBNNy42NTgsODUuMDk4IEM3LjU1OCw4NS4wOTggNy40NTUsODUuMTI3IDcuMzUxLDg1LjE4OCBDNy4wMzEsODUuMzczIDYuNzgxLDg1LjgwNiA2Ljc4MSw4Ni4xNzMgQzYuNzgxLDg2LjQ4MiA2Ljk2Niw4Ni42OTcgNy4yMyw4Ni42OTcgQzcuMzMsODYuNjk3IDcuNDMzLDg2LjY2NiA3LjUzOCw4Ni42MDcgQzcuODU4LDg2LjQyMiA4LjEwOCw4NS45ODkgOC4xMDgsODUuNjIyIEM4LjEwOCw4NS4zMTMgNy45MjMsODUuMDk4IDcuNjU4LDg1LjA5OCIgaWQ9IkZpbGwtMTciIGZpbGw9IiM4MDk3QTIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNy4yMyw4Ni4zMjIgTDcuMTU0LDg2LjE3MyBDNy4xNTQsODUuOTM4IDcuMzMzLDg1LjYyOSA3LjUzOCw4NS41MTIgTDcuNjU4LDg1LjQ3MSBMNy43MzQsODUuNjIyIEM3LjczNCw4NS44NTYgNy41NTUsODYuMTY0IDcuMzUxLDg2LjI4MiBMNy4yMyw4Ni4zMjIgTTcuNjU4LDg1LjI4NCBDNy41OTQsODUuMjg0IDcuNTIxLDg1LjMwNSA3LjQ0NCw4NS4zNSBDNy4xODEsODUuNTAyIDYuOTY3LDg1Ljg3MSA2Ljk2Nyw4Ni4xNzMgQzYuOTY3LDg2LjM4NyA3LjA3NCw4Ni41MSA3LjIzLDg2LjUxIEM3LjI5NCw4Ni41MSA3LjM2Nyw4Ni40ODkgNy40NDQsODYuNDQ0IEM3LjcwOCw4Ni4yOTIgNy45MjEsODUuOTI1IDcuOTIxLDg1LjYyMiBDNy45MjEsODUuNDA4IDcuODE0LDg1LjI4NCA3LjY1OCw4NS4yODQiIGlkPSJGaWxsLTE4IiBmaWxsPSIjODA5N0EyIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTc3LjI3OCw3Ljc2OSBMNzcuMjc4LDUxLjQzNiBMMTAuMjA4LDkwLjE2IEwxMC4yMDgsNDYuNDkzIEw3Ny4yNzgsNy43NjkiIGlkPSJGaWxsLTE5IiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTEwLjA4Myw5MC4zNzUgTDEwLjA4Myw0Ni40MjEgTDEwLjE0Niw0Ni4zODUgTDc3LjQwMyw3LjU1NCBMNzcuNDAzLDUxLjUwOCBMNzcuMzQxLDUxLjU0NCBMMTAuMDgzLDkwLjM3NSBMMTAuMDgzLDkwLjM3NSBaIE0xMC4zMzMsNDYuNTY0IEwxMC4zMzMsODkuOTQ0IEw3Ny4xNTQsNTEuMzY1IEw3Ny4xNTQsNy45ODUgTDEwLjMzMyw0Ni41NjQgTDEwLjMzMyw0Ni41NjQgWiIgaWQ9IkZpbGwtMjAiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMjUuNzM3LDg4LjY0NyBMMTE4LjA5OCw5MS45ODEgTDExOC4wOTgsODQgTDEwNi42MzksODguNzEzIEwxMDYuNjM5LDk2Ljk4MiBMOTksMTAwLjMxNSBMMTEyLjM2OSwxMDMuOTYxIEwxMjUuNzM3LDg4LjY0NyIgaWQ9IkltcG9ydGVkLUxheWVycy1Db3B5LTIiIGZpbGw9IiM0NTVBNjQiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');
    };

    module.exports = RotateInstructions;

},{"./util.js":22}],16:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /**
     * TODO: Fix up all "new THREE" instantiations to improve performance.
     */
    var SensorSample = _dereq_('./sensor-sample.js');
    var THREE = _dereq_('../three-math.js');
    var Util = _dereq_('../util.js');

    var DEBUG = false;

    /**
     * An implementation of a simple complementary filter, which fuses gyroscope and
     * accelerometer data from the 'devicemotion' event.
     *
     * Accelerometer data is very noisy, but stable over the long term.
     * Gyroscope data is smooth, but tends to drift over the long term.
     *
     * This fusion is relatively simple:
     * 1. Get orientation estimates from accelerometer by applying a low-pass filter
     *    on that data.
     * 2. Get orientation estimates from gyroscope by integrating over time.
     * 3. Combine the two estimates, weighing (1) in the long term, but (2) for the
     *    short term.
     */
    function ComplementaryFilter(kFilter) {
        this.kFilter = kFilter;

        // Raw sensor measurements.
        this.currentAccelMeasurement = new SensorSample();
        this.currentGyroMeasurement = new SensorSample();
        this.previousGyroMeasurement = new SensorSample();

        // Current filter orientation
        this.filterQ = new THREE.Quaternion();
        this.previousFilterQ = new THREE.Quaternion();

        // Orientation based on the accelerometer.
        this.accelQ = new THREE.Quaternion();
        // Whether or not the orientation has been initialized.
        this.isOrientationInitialized = false;
        // Running estimate of gravity based on the current orientation.
        this.estimatedGravity = new THREE.Vector3();
        // Measured gravity based on accelerometer.
        this.measuredGravity = new THREE.Vector3();

        // Debug only quaternion of gyro-based orientation.
        this.gyroIntegralQ = new THREE.Quaternion();
    }

    ComplementaryFilter.prototype.addAccelMeasurement = function(vector, timestampS) {
        this.currentAccelMeasurement.set(vector, timestampS);
    };

    ComplementaryFilter.prototype.addGyroMeasurement = function(vector, timestampS) {
        this.currentGyroMeasurement.set(vector, timestampS);

        var deltaT = timestampS - this.previousGyroMeasurement.timestampS;
        if (Util.isTimestampDeltaValid(deltaT)) {
            this.run_();
        }

        this.previousGyroMeasurement.copy(this.currentGyroMeasurement);
    };

    ComplementaryFilter.prototype.run_ = function() {

        if (!this.isOrientationInitialized) {
            this.accelQ = this.accelToQuaternion_(this.currentAccelMeasurement.sample);
            this.previousFilterQ.copy(this.accelQ);
            this.isOrientationInitialized = true;
            return;
        }

        var deltaT = this.currentGyroMeasurement.timestampS -
            this.previousGyroMeasurement.timestampS;

        // Convert gyro rotation vector to a quaternion delta.
        var gyroDeltaQ = this.gyroToQuaternionDelta_(this.currentGyroMeasurement.sample, deltaT);
        this.gyroIntegralQ.multiply(gyroDeltaQ);

        // filter_1 = K * (filter_0 + gyro * dT) + (1 - K) * accel.
        this.filterQ.copy(this.previousFilterQ);
        this.filterQ.multiply(gyroDeltaQ);

        // Calculate the delta between the current estimated gravity and the real
        // gravity vector from accelerometer.
        var invFilterQ = new THREE.Quaternion();
        invFilterQ.copy(this.filterQ);
        invFilterQ.inverse();

        this.estimatedGravity.set(0, 0, -1);
        this.estimatedGravity.applyQuaternion(invFilterQ);
        this.estimatedGravity.normalize();

        this.measuredGravity.copy(this.currentAccelMeasurement.sample);
        this.measuredGravity.normalize();

        // Compare estimated gravity with measured gravity, get the delta quaternion
        // between the two.
        var deltaQ = new THREE.Quaternion();
        deltaQ.setFromUnitVectors(this.estimatedGravity, this.measuredGravity);
        deltaQ.inverse();

        if (DEBUG) {
            console.log('Delta: %d deg, G_est: (%s, %s, %s), G_meas: (%s, %s, %s)',
                THREE.Math.radToDeg(Util.getQuaternionAngle(deltaQ)),
                (this.estimatedGravity.x).toFixed(1),
                (this.estimatedGravity.y).toFixed(1),
                (this.estimatedGravity.z).toFixed(1),
                (this.measuredGravity.x).toFixed(1),
                (this.measuredGravity.y).toFixed(1),
                (this.measuredGravity.z).toFixed(1));
        }

        // Calculate the SLERP target: current orientation plus the measured-estimated
        // quaternion delta.
        var targetQ = new THREE.Quaternion();
        targetQ.copy(this.filterQ);
        targetQ.multiply(deltaQ);

        // SLERP factor: 0 is pure gyro, 1 is pure accel.
        this.filterQ.slerp(targetQ, 1 - this.kFilter);

        this.previousFilterQ.copy(this.filterQ);
    };

    ComplementaryFilter.prototype.getOrientation = function() {
        return this.filterQ;
    };

    ComplementaryFilter.prototype.accelToQuaternion_ = function(accel) {
        var normAccel = new THREE.Vector3();
        normAccel.copy(accel);
        normAccel.normalize();
        var quat = new THREE.Quaternion();
        quat.setFromUnitVectors(new THREE.Vector3(0, 0, -1), normAccel);
        quat.inverse();
        return quat;
    };

    ComplementaryFilter.prototype.gyroToQuaternionDelta_ = function(gyro, dt) {
        // Extract axis and angle from the gyroscope data.
        var quat = new THREE.Quaternion();
        var axis = new THREE.Vector3();
        axis.copy(gyro);
        axis.normalize();
        quat.setFromAxisAngle(axis, gyro.length() * dt);
        return quat;
    };


    module.exports = ComplementaryFilter;

},{"../three-math.js":20,"../util.js":22,"./sensor-sample.js":19}],17:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    var ComplementaryFilter = _dereq_('./complementary-filter.js');
    var PosePredictor = _dereq_('./pose-predictor.js');
    var TouchPanner = _dereq_('../touch-panner.js');
    var THREE = _dereq_('../three-math.js');
    var Util = _dereq_('../util.js');

    /**
     * The pose sensor, implemented using DeviceMotion APIs.
     */
    function FusionPoseSensor() {
        this.deviceId = 'webvr-polyfill:fused';
        this.deviceName = 'VR Position Device (webvr-polyfill:fused)';

        this.accelerometer = new THREE.Vector3();
        this.gyroscope = new THREE.Vector3();

        window.addEventListener('devicemotion', this.onDeviceMotionChange_.bind(this));
        window.addEventListener('orientationchange', this.onScreenOrientationChange_.bind(this));

        this.filter = new ComplementaryFilter(WebVRConfig.K_FILTER || 0.98);
        this.posePredictor = new PosePredictor(WebVRConfig.PREDICTION_TIME_S || 0.040);
        this.touchPanner = new TouchPanner();

        this.filterToWorldQ = new THREE.Quaternion();

        // Set the filter to world transform, depending on OS.
        if (Util.isIOS()) {
            this.filterToWorldQ.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI/2);
        } else {
            this.filterToWorldQ.setFromAxisAngle(new THREE.Vector3(1, 0, 0), -Math.PI/2);
        }

        this.worldToScreenQ = new THREE.Quaternion();
        this.setScreenTransform_();

        // Keep track of a reset transform for resetSensor.
        this.resetQ = new THREE.Quaternion();

        this.isFirefoxAndroid = Util.isFirefoxAndroid();
        this.isIOS = Util.isIOS();

        this.orientationOut_ = new Float32Array(4);
    }

    FusionPoseSensor.prototype.getPosition = function() {
        // This PoseSensor doesn't support position
        return null;
    };

    FusionPoseSensor.prototype.getOrientation = function() {
        // Convert from filter space to the the same system used by the
        // deviceorientation event.
        var orientation = this.filter.getOrientation();

        // Predict orientation.
        this.predictedQ = this.posePredictor.getPrediction(orientation, this.gyroscope, this.previousTimestampS);

        // Convert to THREE coordinate system: -Z forward, Y up, X right.
        var out = new THREE.Quaternion();
        out.copy(this.filterToWorldQ);
        out.multiply(this.resetQ);
        if (!WebVRConfig.TOUCH_PANNER_DISABLED) {
            out.multiply(this.touchPanner.getOrientation());
        }
        out.multiply(this.predictedQ);
        out.multiply(this.worldToScreenQ);

        // Handle the yaw-only case.
        if (WebVRConfig.YAW_ONLY) {
            // Make a quaternion that only turns around the Y-axis.
            out.x = 0;
            out.z = 0;
            out.normalize();
        }

        this.orientationOut_[0] = out.x;
        this.orientationOut_[1] = out.y;
        this.orientationOut_[2] = out.z;
        this.orientationOut_[3] = out.w;
        return this.orientationOut_;
    };

    FusionPoseSensor.prototype.resetPose = function() {
        // Reduce to inverted yaw-only
        this.resetQ.copy(this.filter.getOrientation());
        this.resetQ.x = 0;
        this.resetQ.y = 0;
        this.resetQ.z *= -1;
        this.resetQ.normalize();

        if (!WebVRConfig.TOUCH_PANNER_DISABLED) {
            this.touchPanner.resetSensor();
        }
    };

    FusionPoseSensor.prototype.onDeviceMotionChange_ = function(deviceMotion) {
        var accGravity = deviceMotion.accelerationIncludingGravity;
        var rotRate = deviceMotion.rotationRate;
        var timestampS = deviceMotion.timeStamp / 1000;
        //var timestampS =Date.now()/1000;
        // Firefox Android timeStamp returns one thousandth of a millisecond.
        if (this.isFirefoxAndroid) {
            timestampS /= 1000;
        }

        var deltaS = timestampS - this.previousTimestampS;
        if (deltaS <= Util.MIN_TIMESTEP || deltaS > Util.MAX_TIMESTEP) {
            //console.warn('Invalid timestamps detected. Time step between successive ' +
            //    'gyroscope sensor samples is very small or not monotonic');
            this.previousTimestampS = timestampS;
            return;
        }
        this.accelerometer.set(-accGravity.x, -accGravity.y, -accGravity.z);
        this.gyroscope.set(rotRate.alpha, rotRate.beta, rotRate.gamma);

        // With iOS and Firefox Android, rotationRate is reported in degrees,
        // so we first convert to radians.
        if (this.isIOS || this.isFirefoxAndroid) {
            this.gyroscope.multiplyScalar(Math.PI / 180);
        }

        this.filter.addAccelMeasurement(this.accelerometer, timestampS);
        this.filter.addGyroMeasurement(this.gyroscope, timestampS);

        this.previousTimestampS = timestampS;
    };

    FusionPoseSensor.prototype.onScreenOrientationChange_ =
        function(screenOrientation) {
            this.setScreenTransform_();
        };

    FusionPoseSensor.prototype.setScreenTransform_ = function() {
        this.worldToScreenQ.set(0, 0, 0, 1);
        switch (window.orientation) {
            case 0:
                break;
            case 90:
                this.worldToScreenQ.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2);
                break;
            case -90:
                this.worldToScreenQ.setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI/2);
                break;
            case 180:
                // TODO.
                break;
        }
    };


    module.exports = FusionPoseSensor;

},{"../three-math.js":20,"../touch-panner.js":21,"../util.js":22,"./complementary-filter.js":16,"./pose-predictor.js":18}],18:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    var THREE = _dereq_('../three-math.js');

    var DEBUG = false;

    /**
     * Given an orientation and the gyroscope data, predicts the future orientation
     * of the head. This makes rendering appear faster.
     *
     * Also see: http://msl.cs.uiuc.edu/~lavalle/papers/LavYerKatAnt14.pdf
     *
     * @param {Number} predictionTimeS time from head movement to the appearance of
     * the corresponding image.
     */
    function PosePredictor(predictionTimeS) {
        this.predictionTimeS = predictionTimeS;

        // The quaternion corresponding to the previous state.
        this.previousQ = new THREE.Quaternion();
        // Previous time a prediction occurred.
        this.previousTimestampS = null;

        // The delta quaternion that adjusts the current pose.
        this.deltaQ = new THREE.Quaternion();
        // The output quaternion.
        this.outQ = new THREE.Quaternion();
    }

    PosePredictor.prototype.getPrediction = function(currentQ, gyro, timestampS) {
        if (!this.previousTimestampS) {
            this.previousQ.copy(currentQ);
            this.previousTimestampS = timestampS;
            return currentQ;
        }

        // Calculate axis and angle based on gyroscope rotation rate data.
        var axis = new THREE.Vector3();
        axis.copy(gyro);
        axis.normalize();

        var angularSpeed = gyro.length();

        // If we're rotating slowly, don't do prediction.
        if (angularSpeed < THREE.Math.degToRad(20)) {
            if (DEBUG) {
                console.log('Moving slowly, at %s deg/s: no prediction',
                    THREE.Math.radToDeg(angularSpeed).toFixed(1));
            }
            this.outQ.copy(currentQ);
            this.previousQ.copy(currentQ);
            return this.outQ;
        }

        // Get the predicted angle based on the time delta and latency.
        var deltaT = timestampS - this.previousTimestampS;
        var predictAngle = angularSpeed * this.predictionTimeS;

        this.deltaQ.setFromAxisAngle(axis, predictAngle);
        this.outQ.copy(this.previousQ);
        this.outQ.multiply(this.deltaQ);

        this.previousQ.copy(currentQ);

        return this.outQ;
    };


    module.exports = PosePredictor;

},{"../three-math.js":20}],19:[function(_dereq_,module,exports){
    function SensorSample(sample, timestampS) {
        this.set(sample, timestampS);
    };

    SensorSample.prototype.set = function(sample, timestampS) {
        this.sample = sample;
        this.timestampS = timestampS;
    };

    SensorSample.prototype.copy = function(sensorSample) {
        this.set(sensorSample.sample, sensorSample.timestampS);
    };

    module.exports = SensorSample;

},{}],20:[function(_dereq_,module,exports){
    /*
     * A subset of THREE.js, providing mostly quaternion and euler-related
     * operations, manually lifted from
     * https://github.com/mrdoob/three.js/tree/master/src/math, as of 9c30286b38df039fca389989ff06ea1c15d6bad1
     */

// Only use if the real THREE is not provided.
    var THREE = window.THREE || {};

// If some piece of THREE is missing, fill it in here.
    if (!THREE.Quaternion || !THREE.Vector3 || !THREE.Vector2 || !THREE.Euler || !THREE.Math) {
        console.log('No THREE.js found.');


        /*** START Quaternion ***/

        /**
         * @author mikael emtinger / http://gomo.se/
         * @author alteredq / http://alteredqualia.com/
         * @author WestLangley / http://github.com/WestLangley
         * @author bhouston / http://exocortex.com
         */

        THREE.Quaternion = function ( x, y, z, w ) {

            this._x = x || 0;
            this._y = y || 0;
            this._z = z || 0;
            this._w = ( w !== undefined ) ? w : 1;

        };

        THREE.Quaternion.prototype = {

            constructor: THREE.Quaternion,

            _x: 0,_y: 0, _z: 0, _w: 0,

            get x () {

                return this._x;

            },

            set x ( value ) {

                this._x = value;
                this.onChangeCallback();

            },

            get y () {

                return this._y;

            },

            set y ( value ) {

                this._y = value;
                this.onChangeCallback();

            },

            get z () {

                return this._z;

            },

            set z ( value ) {

                this._z = value;
                this.onChangeCallback();

            },

            get w () {

                return this._w;

            },

            set w ( value ) {

                this._w = value;
                this.onChangeCallback();

            },

            set: function ( x, y, z, w ) {

                this._x = x;
                this._y = y;
                this._z = z;
                this._w = w;

                this.onChangeCallback();

                return this;

            },

            copy: function ( quaternion ) {

                this._x = quaternion.x;
                this._y = quaternion.y;
                this._z = quaternion.z;
                this._w = quaternion.w;

                this.onChangeCallback();

                return this;

            },

            setFromEuler: function ( euler, update ) {

                if ( euler instanceof THREE.Euler === false ) {

                    throw new Error( 'THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );
                }

                // http://www.mathworks.com/matlabcentral/fileexchange/
                // 	20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
                //	content/SpinCalc.m

                var c1 = Math.cos( euler._x / 2 );
                var c2 = Math.cos( euler._y / 2 );
                var c3 = Math.cos( euler._z / 2 );
                var s1 = Math.sin( euler._x / 2 );
                var s2 = Math.sin( euler._y / 2 );
                var s3 = Math.sin( euler._z / 2 );

                if ( euler.order === 'XYZ' ) {

                    this._x = s1 * c2 * c3 + c1 * s2 * s3;
                    this._y = c1 * s2 * c3 - s1 * c2 * s3;
                    this._z = c1 * c2 * s3 + s1 * s2 * c3;
                    this._w = c1 * c2 * c3 - s1 * s2 * s3;

                } else if ( euler.order === 'YXZ' ) {

                    this._x = s1 * c2 * c3 + c1 * s2 * s3;
                    this._y = c1 * s2 * c3 - s1 * c2 * s3;
                    this._z = c1 * c2 * s3 - s1 * s2 * c3;
                    this._w = c1 * c2 * c3 + s1 * s2 * s3;

                } else if ( euler.order === 'ZXY' ) {

                    this._x = s1 * c2 * c3 - c1 * s2 * s3;
                    this._y = c1 * s2 * c3 + s1 * c2 * s3;
                    this._z = c1 * c2 * s3 + s1 * s2 * c3;
                    this._w = c1 * c2 * c3 - s1 * s2 * s3;

                } else if ( euler.order === 'ZYX' ) {

                    this._x = s1 * c2 * c3 - c1 * s2 * s3;
                    this._y = c1 * s2 * c3 + s1 * c2 * s3;
                    this._z = c1 * c2 * s3 - s1 * s2 * c3;
                    this._w = c1 * c2 * c3 + s1 * s2 * s3;

                } else if ( euler.order === 'YZX' ) {

                    this._x = s1 * c2 * c3 + c1 * s2 * s3;
                    this._y = c1 * s2 * c3 + s1 * c2 * s3;
                    this._z = c1 * c2 * s3 - s1 * s2 * c3;
                    this._w = c1 * c2 * c3 - s1 * s2 * s3;

                } else if ( euler.order === 'XZY' ) {

                    this._x = s1 * c2 * c3 - c1 * s2 * s3;
                    this._y = c1 * s2 * c3 - s1 * c2 * s3;
                    this._z = c1 * c2 * s3 + s1 * s2 * c3;
                    this._w = c1 * c2 * c3 + s1 * s2 * s3;

                }

                if ( update !== false ) this.onChangeCallback();

                return this;

            },

            setFromAxisAngle: function ( axis, angle ) {

                // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm

                // assumes axis is normalized

                var halfAngle = angle / 2, s = Math.sin( halfAngle );

                this._x = axis.x * s;
                this._y = axis.y * s;
                this._z = axis.z * s;
                this._w = Math.cos( halfAngle );

                this.onChangeCallback();

                return this;

            },

            setFromRotationMatrix: function ( m ) {

                // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm

                // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

                var te = m.elements,

                    m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
                    m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
                    m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],

                    trace = m11 + m22 + m33,
                    s;

                if ( trace > 0 ) {

                    s = 0.5 / Math.sqrt( trace + 1.0 );

                    this._w = 0.25 / s;
                    this._x = ( m32 - m23 ) * s;
                    this._y = ( m13 - m31 ) * s;
                    this._z = ( m21 - m12 ) * s;

                } else if ( m11 > m22 && m11 > m33 ) {

                    s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );

                    this._w = ( m32 - m23 ) / s;
                    this._x = 0.25 * s;
                    this._y = ( m12 + m21 ) / s;
                    this._z = ( m13 + m31 ) / s;

                } else if ( m22 > m33 ) {

                    s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );

                    this._w = ( m13 - m31 ) / s;
                    this._x = ( m12 + m21 ) / s;
                    this._y = 0.25 * s;
                    this._z = ( m23 + m32 ) / s;

                } else {

                    s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );

                    this._w = ( m21 - m12 ) / s;
                    this._x = ( m13 + m31 ) / s;
                    this._y = ( m23 + m32 ) / s;
                    this._z = 0.25 * s;

                }

                this.onChangeCallback();

                return this;

            },

            setFromUnitVectors: function () {

                // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final

                // assumes direction vectors vFrom and vTo are normalized

                var v1, r;

                var EPS = 0.000001;

                return function ( vFrom, vTo ) {

                    if ( v1 === undefined ) v1 = new THREE.Vector3();

                    r = vFrom.dot( vTo ) + 1;

                    if ( r < EPS ) {

                        r = 0;

                        if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {

                            v1.set( - vFrom.y, vFrom.x, 0 );

                        } else {

                            v1.set( 0, - vFrom.z, vFrom.y );

                        }

                    } else {

                        v1.crossVectors( vFrom, vTo );

                    }

                    this._x = v1.x;
                    this._y = v1.y;
                    this._z = v1.z;
                    this._w = r;

                    this.normalize();

                    return this;

                }

            }(),

            inverse: function () {

                this.conjugate().normalize();

                return this;

            },

            conjugate: function () {

                this._x *= - 1;
                this._y *= - 1;
                this._z *= - 1;

                this.onChangeCallback();

                return this;

            },

            dot: function ( v ) {

                return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;

            },

            lengthSq: function () {

                return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;

            },

            length: function () {

                return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );

            },

            normalize: function () {

                var l = this.length();

                if ( l === 0 ) {

                    this._x = 0;
                    this._y = 0;
                    this._z = 0;
                    this._w = 1;

                } else {

                    l = 1 / l;

                    this._x = this._x * l;
                    this._y = this._y * l;
                    this._z = this._z * l;
                    this._w = this._w * l;

                }

                this.onChangeCallback();

                return this;

            },

            multiply: function ( q, p ) {

                if ( p !== undefined ) {

                    console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
                    return this.multiplyQuaternions( q, p );

                }

                return this.multiplyQuaternions( this, q );

            },

            multiplyQuaternions: function ( a, b ) {

                // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm

                var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
                var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;

                this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
                this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
                this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
                this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;

                this.onChangeCallback();

                return this;

            },

            multiplyVector3: function ( vector ) {

                console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
                return vector.applyQuaternion( this );

            },

            slerp: function ( qb, t ) {

                if ( t === 0 ) return this;
                if ( t === 1 ) return this.copy( qb );

                var x = this._x, y = this._y, z = this._z, w = this._w;

                // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/

                var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;

                if ( cosHalfTheta < 0 ) {

                    this._w = - qb._w;
                    this._x = - qb._x;
                    this._y = - qb._y;
                    this._z = - qb._z;

                    cosHalfTheta = - cosHalfTheta;

                } else {

                    this.copy( qb );

                }

                if ( cosHalfTheta >= 1.0 ) {

                    this._w = w;
                    this._x = x;
                    this._y = y;
                    this._z = z;

                    return this;

                }

                var halfTheta = Math.acos( cosHalfTheta );
                var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );

                if ( Math.abs( sinHalfTheta ) < 0.001 ) {

                    this._w = 0.5 * ( w + this._w );
                    this._x = 0.5 * ( x + this._x );
                    this._y = 0.5 * ( y + this._y );
                    this._z = 0.5 * ( z + this._z );

                    return this;

                }

                var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
                    ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;

                this._w = ( w * ratioA + this._w * ratioB );
                this._x = ( x * ratioA + this._x * ratioB );
                this._y = ( y * ratioA + this._y * ratioB );
                this._z = ( z * ratioA + this._z * ratioB );

                this.onChangeCallback();

                return this;

            },

            equals: function ( quaternion ) {

                return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );

            },

            fromArray: function ( array, offset ) {

                if ( offset === undefined ) offset = 0;

                this._x = array[ offset ];
                this._y = array[ offset + 1 ];
                this._z = array[ offset + 2 ];
                this._w = array[ offset + 3 ];

                this.onChangeCallback();

                return this;

            },

            toArray: function ( array, offset ) {

                if ( array === undefined ) array = [];
                if ( offset === undefined ) offset = 0;

                array[ offset ] = this._x;
                array[ offset + 1 ] = this._y;
                array[ offset + 2 ] = this._z;
                array[ offset + 3 ] = this._w;

                return array;

            },

            onChange: function ( callback ) {

                this.onChangeCallback = callback;

                return this;

            },

            onChangeCallback: function () {},

            clone: function () {

                return new THREE.Quaternion( this._x, this._y, this._z, this._w );

            }

        };

        THREE.Quaternion.slerp = function ( qa, qb, qm, t ) {

            return qm.copy( qa ).slerp( qb, t );

        }

        /*** END Quaternion ***/
        /*** START Vector2 ***/
        /**
         * @author mrdoob / http://mrdoob.com/
         * @author philogb / http://blog.thejit.org/
         * @author egraether / http://egraether.com/
         * @author zz85 / http://www.lab4games.net/zz85/blog
         */

        THREE.Vector2 = function ( x, y ) {

            this.x = x || 0;
            this.y = y || 0;

        };

        THREE.Vector2.prototype = {

            constructor: THREE.Vector2,

            set: function ( x, y ) {

                this.x = x;
                this.y = y;

                return this;

            },

            setX: function ( x ) {

                this.x = x;

                return this;

            },

            setY: function ( y ) {

                this.y = y;

                return this;

            },

            setComponent: function ( index, value ) {

                switch ( index ) {

                    case 0: this.x = value; break;
                    case 1: this.y = value; break;
                    default: throw new Error( 'index is out of range: ' + index );

                }

            },

            getComponent: function ( index ) {

                switch ( index ) {

                    case 0: return this.x;
                    case 1: return this.y;
                    default: throw new Error( 'index is out of range: ' + index );

                }

            },

            copy: function ( v ) {

                this.x = v.x;
                this.y = v.y;

                return this;

            },

            add: function ( v, w ) {

                if ( w !== undefined ) {

                    console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
                    return this.addVectors( v, w );

                }

                this.x += v.x;
                this.y += v.y;

                return this;

            },

            addVectors: function ( a, b ) {

                this.x = a.x + b.x;
                this.y = a.y + b.y;

                return this;

            },

            addScalar: function ( s ) {

                this.x += s;
                this.y += s;

                return this;

            },

            sub: function ( v, w ) {

                if ( w !== undefined ) {

                    console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
                    return this.subVectors( v, w );

                }

                this.x -= v.x;
                this.y -= v.y;

                return this;

            },

            subVectors: function ( a, b ) {

                this.x = a.x - b.x;
                this.y = a.y - b.y;

                return this;

            },

            multiply: function ( v ) {

                this.x *= v.x;
                this.y *= v.y;

                return this;

            },

            multiplyScalar: function ( s ) {

                this.x *= s;
                this.y *= s;

                return this;

            },

            divide: function ( v ) {

                this.x /= v.x;
                this.y /= v.y;

                return this;

            },

            divideScalar: function ( scalar ) {

                if ( scalar !== 0 ) {

                    var invScalar = 1 / scalar;

                    this.x *= invScalar;
                    this.y *= invScalar;

                } else {

                    this.x = 0;
                    this.y = 0;

                }

                return this;

            },

            min: function ( v ) {

                if ( this.x > v.x ) {

                    this.x = v.x;

                }

                if ( this.y > v.y ) {

                    this.y = v.y;

                }

                return this;

            },

            max: function ( v ) {

                if ( this.x < v.x ) {

                    this.x = v.x;

                }

                if ( this.y < v.y ) {

                    this.y = v.y;

                }

                return this;

            },

            clamp: function ( min, max ) {

                // This function assumes min < max, if this assumption isn't true it will not operate correctly

                if ( this.x < min.x ) {

                    this.x = min.x;

                } else if ( this.x > max.x ) {

                    this.x = max.x;

                }

                if ( this.y < min.y ) {

                    this.y = min.y;

                } else if ( this.y > max.y ) {

                    this.y = max.y;

                }

                return this;
            },

            clampScalar: ( function () {

                var min, max;

                return function ( minVal, maxVal ) {

                    if ( min === undefined ) {

                        min = new THREE.Vector2();
                        max = new THREE.Vector2();

                    }

                    min.set( minVal, minVal );
                    max.set( maxVal, maxVal );

                    return this.clamp( min, max );

                };

            } )(),

            floor: function () {

                this.x = Math.floor( this.x );
                this.y = Math.floor( this.y );

                return this;

            },

            ceil: function () {

                this.x = Math.ceil( this.x );
                this.y = Math.ceil( this.y );

                return this;

            },

            round: function () {

                this.x = Math.round( this.x );
                this.y = Math.round( this.y );

                return this;

            },

            roundToZero: function () {

                this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
                this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );

                return this;

            },

            negate: function () {

                this.x = - this.x;
                this.y = - this.y;

                return this;

            },

            dot: function ( v ) {

                return this.x * v.x + this.y * v.y;

            },

            lengthSq: function () {

                return this.x * this.x + this.y * this.y;

            },

            length: function () {

                return Math.sqrt( this.x * this.x + this.y * this.y );

            },

            normalize: function () {

                return this.divideScalar( this.length() );

            },

            distanceTo: function ( v ) {

                return Math.sqrt( this.distanceToSquared( v ) );

            },

            distanceToSquared: function ( v ) {

                var dx = this.x - v.x, dy = this.y - v.y;
                return dx * dx + dy * dy;

            },

            setLength: function ( l ) {

                var oldLength = this.length();

                if ( oldLength !== 0 && l !== oldLength ) {

                    this.multiplyScalar( l / oldLength );
                }

                return this;

            },

            lerp: function ( v, alpha ) {

                this.x += ( v.x - this.x ) * alpha;
                this.y += ( v.y - this.y ) * alpha;

                return this;

            },

            equals: function ( v ) {

                return ( ( v.x === this.x ) && ( v.y === this.y ) );

            },

            fromArray: function ( array, offset ) {

                if ( offset === undefined ) offset = 0;

                this.x = array[ offset ];
                this.y = array[ offset + 1 ];

                return this;

            },

            toArray: function ( array, offset ) {

                if ( array === undefined ) array = [];
                if ( offset === undefined ) offset = 0;

                array[ offset ] = this.x;
                array[ offset + 1 ] = this.y;

                return array;

            },

            fromAttribute: function ( attribute, index, offset ) {

                if ( offset === undefined ) offset = 0;

                index = index * attribute.itemSize + offset;

                this.x = attribute.array[ index ];
                this.y = attribute.array[ index + 1 ];

                return this;

            },

            clone: function () {

                return new THREE.Vector2( this.x, this.y );

            }

        };
        /*** END Vector2 ***/
        /*** START Vector3 ***/

        /**
         * @author mrdoob / http://mrdoob.com/
         * @author *kile / http://kile.stravaganza.org/
         * @author philogb / http://blog.thejit.org/
         * @author mikael emtinger / http://gomo.se/
         * @author egraether / http://egraether.com/
         * @author WestLangley / http://github.com/WestLangley
         */

        THREE.Vector3 = function ( x, y, z ) {

            this.x = x || 0;
            this.y = y || 0;
            this.z = z || 0;

        };

        THREE.Vector3.prototype = {

            constructor: THREE.Vector3,

            set: function ( x, y, z ) {

                this.x = x;
                this.y = y;
                this.z = z;

                return this;

            },

            setX: function ( x ) {

                this.x = x;

                return this;

            },

            setY: function ( y ) {

                this.y = y;

                return this;

            },

            setZ: function ( z ) {

                this.z = z;

                return this;

            },

            setComponent: function ( index, value ) {

                switch ( index ) {

                    case 0: this.x = value; break;
                    case 1: this.y = value; break;
                    case 2: this.z = value; break;
                    default: throw new Error( 'index is out of range: ' + index );

                }

            },

            getComponent: function ( index ) {

                switch ( index ) {

                    case 0: return this.x;
                    case 1: return this.y;
                    case 2: return this.z;
                    default: throw new Error( 'index is out of range: ' + index );

                }

            },

            copy: function ( v ) {

                this.x = v.x;
                this.y = v.y;
                this.z = v.z;

                return this;

            },

            add: function ( v, w ) {

                if ( w !== undefined ) {

                    console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
                    return this.addVectors( v, w );

                }

                this.x += v.x;
                this.y += v.y;
                this.z += v.z;

                return this;

            },

            addScalar: function ( s ) {

                this.x += s;
                this.y += s;
                this.z += s;

                return this;

            },

            addVectors: function ( a, b ) {

                this.x = a.x + b.x;
                this.y = a.y + b.y;
                this.z = a.z + b.z;

                return this;

            },

            sub: function ( v, w ) {

                if ( w !== undefined ) {

                    console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
                    return this.subVectors( v, w );

                }

                this.x -= v.x;
                this.y -= v.y;
                this.z -= v.z;

                return this;

            },

            subVectors: function ( a, b ) {

                this.x = a.x - b.x;
                this.y = a.y - b.y;
                this.z = a.z - b.z;

                return this;

            },

            multiply: function ( v, w ) {

                if ( w !== undefined ) {

                    console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
                    return this.multiplyVectors( v, w );

                }

                this.x *= v.x;
                this.y *= v.y;
                this.z *= v.z;

                return this;

            },

            multiplyScalar: function ( scalar ) {

                this.x *= scalar;
                this.y *= scalar;
                this.z *= scalar;

                return this;

            },

            multiplyVectors: function ( a, b ) {

                this.x = a.x * b.x;
                this.y = a.y * b.y;
                this.z = a.z * b.z;

                return this;

            },

            applyEuler: function () {

                var quaternion;

                return function ( euler ) {

                    if ( euler instanceof THREE.Euler === false ) {

                        console.error( 'THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.' );

                    }

                    if ( quaternion === undefined ) quaternion = new THREE.Quaternion();

                    this.applyQuaternion( quaternion.setFromEuler( euler ) );

                    return this;

                };

            }(),

            applyAxisAngle: function () {

                var quaternion;

                return function ( axis, angle ) {

                    if ( quaternion === undefined ) quaternion = new THREE.Quaternion();

                    this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );

                    return this;

                };

            }(),

            applyMatrix3: function ( m ) {

                var x = this.x;
                var y = this.y;
                var z = this.z;

                var e = m.elements;

                this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
                this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
                this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;

                return this;

            },

            applyMatrix4: function ( m ) {

                // input: THREE.Matrix4 affine matrix

                var x = this.x, y = this.y, z = this.z;

                var e = m.elements;

                this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ]  * z + e[ 12 ];
                this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ]  * z + e[ 13 ];
                this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ];

                return this;

            },

            applyProjection: function ( m ) {

                // input: THREE.Matrix4 projection matrix

                var x = this.x, y = this.y, z = this.z;

                var e = m.elements;
                var d = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); // perspective divide

                this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ]  * z + e[ 12 ] ) * d;
                this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ]  * z + e[ 13 ] ) * d;
                this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * d;

                return this;

            },

            applyQuaternion: function ( q ) {

                var x = this.x;
                var y = this.y;
                var z = this.z;

                var qx = q.x;
                var qy = q.y;
                var qz = q.z;
                var qw = q.w;

                // calculate quat * vector

                var ix =  qw * x + qy * z - qz * y;
                var iy =  qw * y + qz * x - qx * z;
                var iz =  qw * z + qx * y - qy * x;
                var iw = - qx * x - qy * y - qz * z;

                // calculate result * inverse quat

                this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;
                this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;
                this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;

                return this;

            },

            project: function () {

                var matrix;

                return function ( camera ) {

                    if ( matrix === undefined ) matrix = new THREE.Matrix4();

                    matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) );
                    return this.applyProjection( matrix );

                };

            }(),

            unproject: function () {

                var matrix;

                return function ( camera ) {

                    if ( matrix === undefined ) matrix = new THREE.Matrix4();

                    matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) );
                    return this.applyProjection( matrix );

                };

            }(),

            transformDirection: function ( m ) {

                // input: THREE.Matrix4 affine matrix
                // vector interpreted as a direction

                var x = this.x, y = this.y, z = this.z;

                var e = m.elements;

                this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ]  * z;
                this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ]  * z;
                this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;

                this.normalize();

                return this;

            },

            divide: function ( v ) {

                this.x /= v.x;
                this.y /= v.y;
                this.z /= v.z;

                return this;

            },

            divideScalar: function ( scalar ) {

                if ( scalar !== 0 ) {

                    var invScalar = 1 / scalar;

                    this.x *= invScalar;
                    this.y *= invScalar;
                    this.z *= invScalar;

                } else {

                    this.x = 0;
                    this.y = 0;
                    this.z = 0;

                }

                return this;

            },

            min: function ( v ) {

                if ( this.x > v.x ) {

                    this.x = v.x;

                }

                if ( this.y > v.y ) {

                    this.y = v.y;

                }

                if ( this.z > v.z ) {

                    this.z = v.z;

                }

                return this;

            },

            max: function ( v ) {

                if ( this.x < v.x ) {

                    this.x = v.x;

                }

                if ( this.y < v.y ) {

                    this.y = v.y;

                }

                if ( this.z < v.z ) {

                    this.z = v.z;

                }

                return this;

            },

            clamp: function ( min, max ) {

                // This function assumes min < max, if this assumption isn't true it will not operate correctly

                if ( this.x < min.x ) {

                    this.x = min.x;

                } else if ( this.x > max.x ) {

                    this.x = max.x;

                }

                if ( this.y < min.y ) {

                    this.y = min.y;

                } else if ( this.y > max.y ) {

                    this.y = max.y;

                }

                if ( this.z < min.z ) {

                    this.z = min.z;

                } else if ( this.z > max.z ) {

                    this.z = max.z;

                }

                return this;

            },

            clampScalar: ( function () {

                var min, max;

                return function ( minVal, maxVal ) {

                    if ( min === undefined ) {

                        min = new THREE.Vector3();
                        max = new THREE.Vector3();

                    }

                    min.set( minVal, minVal, minVal );
                    max.set( maxVal, maxVal, maxVal );

                    return this.clamp( min, max );

                };

            } )(),

            floor: function () {

                this.x = Math.floor( this.x );
                this.y = Math.floor( this.y );
                this.z = Math.floor( this.z );

                return this;

            },

            ceil: function () {

                this.x = Math.ceil( this.x );
                this.y = Math.ceil( this.y );
                this.z = Math.ceil( this.z );

                return this;

            },

            round: function () {

                this.x = Math.round( this.x );
                this.y = Math.round( this.y );
                this.z = Math.round( this.z );

                return this;

            },

            roundToZero: function () {

                this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
                this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
                this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );

                return this;

            },

            negate: function () {

                this.x = - this.x;
                this.y = - this.y;
                this.z = - this.z;

                return this;

            },

            dot: function ( v ) {

                return this.x * v.x + this.y * v.y + this.z * v.z;

            },

            lengthSq: function () {

                return this.x * this.x + this.y * this.y + this.z * this.z;

            },

            length: function () {

                return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );

            },

            lengthManhattan: function () {

                return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );

            },

            normalize: function () {

                return this.divideScalar( this.length() );

            },

            setLength: function ( l ) {

                var oldLength = this.length();

                if ( oldLength !== 0 && l !== oldLength  ) {

                    this.multiplyScalar( l / oldLength );
                }

                return this;

            },

            lerp: function ( v, alpha ) {

                this.x += ( v.x - this.x ) * alpha;
                this.y += ( v.y - this.y ) * alpha;
                this.z += ( v.z - this.z ) * alpha;

                return this;

            },

            cross: function ( v, w ) {

                if ( w !== undefined ) {

                    console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
                    return this.crossVectors( v, w );

                }

                var x = this.x, y = this.y, z = this.z;

                this.x = y * v.z - z * v.y;
                this.y = z * v.x - x * v.z;
                this.z = x * v.y - y * v.x;

                return this;

            },

            crossVectors: function ( a, b ) {

                var ax = a.x, ay = a.y, az = a.z;
                var bx = b.x, by = b.y, bz = b.z;

                this.x = ay * bz - az * by;
                this.y = az * bx - ax * bz;
                this.z = ax * by - ay * bx;

                return this;

            },

            projectOnVector: function () {

                var v1, dot;

                return function ( vector ) {

                    if ( v1 === undefined ) v1 = new THREE.Vector3();

                    v1.copy( vector ).normalize();

                    dot = this.dot( v1 );

                    return this.copy( v1 ).multiplyScalar( dot );

                };

            }(),

            projectOnPlane: function () {

                var v1;

                return function ( planeNormal ) {

                    if ( v1 === undefined ) v1 = new THREE.Vector3();

                    v1.copy( this ).projectOnVector( planeNormal );

                    return this.sub( v1 );

                }

            }(),

            reflect: function () {

                // reflect incident vector off plane orthogonal to normal
                // normal is assumed to have unit length

                var v1;

                return function ( normal ) {

                    if ( v1 === undefined ) v1 = new THREE.Vector3();

                    return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );

                }

            }(),

            angleTo: function ( v ) {

                var theta = this.dot( v ) / ( this.length() * v.length() );

                // clamp, to handle numerical problems

                return Math.acos( THREE.Math.clamp( theta, - 1, 1 ) );

            },

            distanceTo: function ( v ) {

                return Math.sqrt( this.distanceToSquared( v ) );

            },

            distanceToSquared: function ( v ) {

                var dx = this.x - v.x;
                var dy = this.y - v.y;
                var dz = this.z - v.z;

                return dx * dx + dy * dy + dz * dz;

            },

            setEulerFromRotationMatrix: function ( m, order ) {

                console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );

            },

            setEulerFromQuaternion: function ( q, order ) {

                console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );

            },

            getPositionFromMatrix: function ( m ) {

                console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );

                return this.setFromMatrixPosition( m );

            },

            getScaleFromMatrix: function ( m ) {

                console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );

                return this.setFromMatrixScale( m );
            },

            getColumnFromMatrix: function ( index, matrix ) {

                console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );

                return this.setFromMatrixColumn( index, matrix );

            },

            setFromMatrixPosition: function ( m ) {

                this.x = m.elements[ 12 ];
                this.y = m.elements[ 13 ];
                this.z = m.elements[ 14 ];

                return this;

            },

            setFromMatrixScale: function ( m ) {

                var sx = this.set( m.elements[ 0 ], m.elements[ 1 ], m.elements[  2 ] ).length();
                var sy = this.set( m.elements[ 4 ], m.elements[ 5 ], m.elements[  6 ] ).length();
                var sz = this.set( m.elements[ 8 ], m.elements[ 9 ], m.elements[ 10 ] ).length();

                this.x = sx;
                this.y = sy;
                this.z = sz;

                return this;
            },

            setFromMatrixColumn: function ( index, matrix ) {

                var offset = index * 4;

                var me = matrix.elements;

                this.x = me[ offset ];
                this.y = me[ offset + 1 ];
                this.z = me[ offset + 2 ];

                return this;

            },

            equals: function ( v ) {

                return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );

            },

            fromArray: function ( array, offset ) {

                if ( offset === undefined ) offset = 0;

                this.x = array[ offset ];
                this.y = array[ offset + 1 ];
                this.z = array[ offset + 2 ];

                return this;

            },

            toArray: function ( array, offset ) {

                if ( array === undefined ) array = [];
                if ( offset === undefined ) offset = 0;

                array[ offset ] = this.x;
                array[ offset + 1 ] = this.y;
                array[ offset + 2 ] = this.z;

                return array;

            },

            fromAttribute: function ( attribute, index, offset ) {

                if ( offset === undefined ) offset = 0;

                index = index * attribute.itemSize + offset;

                this.x = attribute.array[ index ];
                this.y = attribute.array[ index + 1 ];
                this.z = attribute.array[ index + 2 ];

                return this;

            },

            clone: function () {

                return new THREE.Vector3( this.x, this.y, this.z );

            }

        };
        /*** END Vector3 ***/
        /*** START Euler ***/
        /**
         * @author mrdoob / http://mrdoob.com/
         * @author WestLangley / http://github.com/WestLangley
         * @author bhouston / http://exocortex.com
         */

        THREE.Euler = function ( x, y, z, order ) {

            this._x = x || 0;
            this._y = y || 0;
            this._z = z || 0;
            this._order = order || THREE.Euler.DefaultOrder;

        };

        THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];

        THREE.Euler.DefaultOrder = 'XYZ';

        THREE.Euler.prototype = {

            constructor: THREE.Euler,

            _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder,

            get x () {

                return this._x;

            },

            set x ( value ) {

                this._x = value;
                this.onChangeCallback();

            },

            get y () {

                return this._y;

            },

            set y ( value ) {

                this._y = value;
                this.onChangeCallback();

            },

            get z () {

                return this._z;

            },

            set z ( value ) {

                this._z = value;
                this.onChangeCallback();

            },

            get order () {

                return this._order;

            },

            set order ( value ) {

                this._order = value;
                this.onChangeCallback();

            },

            set: function ( x, y, z, order ) {

                this._x = x;
                this._y = y;
                this._z = z;
                this._order = order || this._order;

                this.onChangeCallback();

                return this;

            },

            copy: function ( euler ) {

                this._x = euler._x;
                this._y = euler._y;
                this._z = euler._z;
                this._order = euler._order;

                this.onChangeCallback();

                return this;

            },

            setFromRotationMatrix: function ( m, order, update ) {

                var clamp = THREE.Math.clamp;

                // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

                var te = m.elements;
                var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];
                var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];
                var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];

                order = order || this._order;

                if ( order === 'XYZ' ) {

                    this._y = Math.asin( clamp( m13, - 1, 1 ) );

                    if ( Math.abs( m13 ) < 0.99999 ) {

                        this._x = Math.atan2( - m23, m33 );
                        this._z = Math.atan2( - m12, m11 );

                    } else {

                        this._x = Math.atan2( m32, m22 );
                        this._z = 0;

                    }

                } else if ( order === 'YXZ' ) {

                    this._x = Math.asin( - clamp( m23, - 1, 1 ) );

                    if ( Math.abs( m23 ) < 0.99999 ) {

                        this._y = Math.atan2( m13, m33 );
                        this._z = Math.atan2( m21, m22 );

                    } else {

                        this._y = Math.atan2( - m31, m11 );
                        this._z = 0;

                    }

                } else if ( order === 'ZXY' ) {

                    this._x = Math.asin( clamp( m32, - 1, 1 ) );

                    if ( Math.abs( m32 ) < 0.99999 ) {

                        this._y = Math.atan2( - m31, m33 );
                        this._z = Math.atan2( - m12, m22 );

                    } else {

                        this._y = 0;
                        this._z = Math.atan2( m21, m11 );

                    }

                } else if ( order === 'ZYX' ) {

                    this._y = Math.asin( - clamp( m31, - 1, 1 ) );

                    if ( Math.abs( m31 ) < 0.99999 ) {

                        this._x = Math.atan2( m32, m33 );
                        this._z = Math.atan2( m21, m11 );

                    } else {

                        this._x = 0;
                        this._z = Math.atan2( - m12, m22 );

                    }

                } else if ( order === 'YZX' ) {

                    this._z = Math.asin( clamp( m21, - 1, 1 ) );

                    if ( Math.abs( m21 ) < 0.99999 ) {

                        this._x = Math.atan2( - m23, m22 );
                        this._y = Math.atan2( - m31, m11 );

                    } else {

                        this._x = 0;
                        this._y = Math.atan2( m13, m33 );

                    }

                } else if ( order === 'XZY' ) {

                    this._z = Math.asin( - clamp( m12, - 1, 1 ) );

                    if ( Math.abs( m12 ) < 0.99999 ) {

                        this._x = Math.atan2( m32, m22 );
                        this._y = Math.atan2( m13, m11 );

                    } else {

                        this._x = Math.atan2( - m23, m33 );
                        this._y = 0;

                    }

                } else {

                    console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order )

                }

                this._order = order;

                if ( update !== false ) this.onChangeCallback();

                return this;

            },

            setFromQuaternion: function () {

                var matrix;

                return function ( q, order, update ) {

                    if ( matrix === undefined ) matrix = new THREE.Matrix4();
                    matrix.makeRotationFromQuaternion( q );
                    this.setFromRotationMatrix( matrix, order, update );

                    return this;

                };

            }(),

            setFromVector3: function ( v, order ) {

                return this.set( v.x, v.y, v.z, order || this._order );

            },

            reorder: function () {

                // WARNING: this discards revolution information -bhouston

                var q = new THREE.Quaternion();

                return function ( newOrder ) {

                    q.setFromEuler( this );
                    this.setFromQuaternion( q, newOrder );

                };

            }(),

            equals: function ( euler ) {

                return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );

            },

            fromArray: function ( array ) {

                this._x = array[ 0 ];
                this._y = array[ 1 ];
                this._z = array[ 2 ];
                if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];

                this.onChangeCallback();

                return this;

            },

            toArray: function () {

                return [ this._x, this._y, this._z, this._order ];

            },

            toVector3: function ( optionalResult ) {

                if ( optionalResult ) {

                    return optionalResult.set( this._x, this._y, this._z );

                } else {

                    return new THREE.Vector3( this._x, this._y, this._z );

                }

            },

            onChange: function ( callback ) {

                this.onChangeCallback = callback;

                return this;

            },

            onChangeCallback: function () {},

            clone: function () {

                return new THREE.Euler( this._x, this._y, this._z, this._order );

            }

        };
        /*** END Euler ***/
        /*** START Math ***/
        /**
         * @author alteredq / http://alteredqualia.com/
         * @author mrdoob / http://mrdoob.com/
         */

        THREE.Math = {

            generateUUID: function () {

                // http://www.broofa.com/Tools/Math.uuid.htm

                var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split( '' );
                var uuid = new Array( 36 );
                var rnd = 0, r;

                return function () {

                    for ( var i = 0; i < 36; i ++ ) {

                        if ( i == 8 || i == 13 || i == 18 || i == 23 ) {

                            uuid[ i ] = '-';

                        } else if ( i == 14 ) {

                            uuid[ i ] = '4';

                        } else {

                            if ( rnd <= 0x02 ) rnd = 0x2000000 + ( Math.random() * 0x1000000 ) | 0;
                            r = rnd & 0xf;
                            rnd = rnd >> 4;
                            uuid[ i ] = chars[ ( i == 19 ) ? ( r & 0x3 ) | 0x8 : r ];

                        }
                    }

                    return uuid.join( '' );

                };

            }(),

            // Clamp value to range <a, b>

            clamp: function ( x, a, b ) {

                return ( x < a ) ? a : ( ( x > b ) ? b : x );

            },

            // Clamp value to range <a, inf)

            clampBottom: function ( x, a ) {

                return x < a ? a : x;

            },

            // Linear mapping from range <a1, a2> to range <b1, b2>

            mapLinear: function ( x, a1, a2, b1, b2 ) {

                return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );

            },

            // http://en.wikipedia.org/wiki/Smoothstep

            smoothstep: function ( x, min, max ) {

                if ( x <= min ) return 0;
                if ( x >= max ) return 1;

                x = ( x - min ) / ( max - min );

                return x * x * ( 3 - 2 * x );

            },

            smootherstep: function ( x, min, max ) {

                if ( x <= min ) return 0;
                if ( x >= max ) return 1;

                x = ( x - min ) / ( max - min );

                return x * x * x * ( x * ( x * 6 - 15 ) + 10 );

            },

            // Random float from <0, 1> with 16 bits of randomness
            // (standard Math.random() creates repetitive patterns when applied over larger space)

            random16: function () {

                return ( 65280 * Math.random() + 255 * Math.random() ) / 65535;

            },

            // Random integer from <low, high> interval

            randInt: function ( low, high ) {

                return Math.floor( this.randFloat( low, high ) );

            },

            // Random float from <low, high> interval

            randFloat: function ( low, high ) {

                return low + Math.random() * ( high - low );

            },

            // Random float from <-range/2, range/2> interval

            randFloatSpread: function ( range ) {

                return range * ( 0.5 - Math.random() );

            },

            degToRad: function () {

                var degreeToRadiansFactor = Math.PI / 180;

                return function ( degrees ) {

                    return degrees * degreeToRadiansFactor;

                };

            }(),

            radToDeg: function () {

                var radianToDegreesFactor = 180 / Math.PI;

                return function ( radians ) {

                    return radians * radianToDegreesFactor;

                };

            }(),

            isPowerOfTwo: function ( value ) {

                return ( value & ( value - 1 ) ) === 0 && value !== 0;

            },

            nextPowerOfTwo: function ( value ) {

                value --;
                value |= value >> 1;
                value |= value >> 2;
                value |= value >> 4;
                value |= value >> 8;
                value |= value >> 16;
                value ++;

                return value;
            }

        };

        /*** END Math ***/

    }

    module.exports = THREE;

},{}],21:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    var THREE = _dereq_('./three-math.js');
    var Util = _dereq_('./util.js');

    var ROTATE_SPEED = 0.5;
    /**
     * Provides a quaternion responsible for pre-panning the scene before further
     * transformations due to device sensors.
     */
    function TouchPanner() {
        window.addEventListener('touchstart', this.onTouchStart_.bind(this));
        window.addEventListener('touchmove', this.onTouchMove_.bind(this));
        window.addEventListener('touchend', this.onTouchEnd_.bind(this));

        this.isTouching = false;
        this.rotateStart = new THREE.Vector2();
        this.rotateEnd = new THREE.Vector2();
        this.rotateDelta = new THREE.Vector2();

        this.theta = 0;
        this.orientation = new THREE.Quaternion();
    }

    TouchPanner.prototype.getOrientation = function() {
        this.orientation.setFromEuler(new THREE.Euler(0, 0, this.theta));
        return this.orientation;
    };

    TouchPanner.prototype.resetSensor = function() {
        this.theta = 0;
    };

    TouchPanner.prototype.onTouchStart_ = function(e) {
        // Only respond if there is exactly one touch.
        if (e.touches.length != 1) {
            return;
        }
        this.rotateStart.set(e.touches[0].pageX, e.touches[0].pageY);
        this.isTouching = true;
    };

    TouchPanner.prototype.onTouchMove_ = function(e) {
        if (!this.isTouching) {
            return;
        }
        this.rotateEnd.set(e.touches[0].pageX, e.touches[0].pageY);
        this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
        this.rotateStart.copy(this.rotateEnd);

        // On iOS, direction is inverted.
        if (Util.isIOS()) {
            this.rotateDelta.x *= -1;
        }

        var element = document.body;
        this.theta += 2 * Math.PI * this.rotateDelta.x / element.clientWidth * ROTATE_SPEED;
    };

    TouchPanner.prototype.onTouchEnd_ = function(e) {
        this.isTouching = false;
    };

    module.exports = TouchPanner;

},{"./three-math.js":20,"./util.js":22}],22:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    var Util = window.Util || {};

    Util.MIN_TIMESTEP = 0.00095;
    Util.MAX_TIMESTEP = 1;

    Util.base64 = function(mimeType, base64) {
        return 'data:' + mimeType + ';base64,' + base64;
    };

    Util.clamp = function(value, min, max) {
        return Math.min(Math.max(min, value), max);
    };

    Util.lerp = function(a, b, t) {
        return a + ((b - a) * t);
    };

    Util.pingPong = function(t, length) {
        if (t < 0) t = -t;

        var mult = Math.floor(t / length);
        var mod = t - (length * mult);
        return mult % 2 ? length - mod : mod;
    };

    Util.isIOS = function() {
        return /iPad|iPhone|iPod/.test(navigator.platform);
    };

    Util.isFirefoxAndroid = function() {
        return navigator.userAgent.indexOf('Firefox') !== -1 &&
            navigator.userAgent.indexOf('Android') !== -1;
    };

    Util.isLandscapeMode = function() {
        return (window.orientation == 90 || window.orientation == -90);
    };

// Helper method to validate the time steps of sensor timestamps.
    Util.isTimestampDeltaValid = function(timestampDeltaS) {
        if (isNaN(timestampDeltaS)) {
            return false;
        }
        if (timestampDeltaS <= Util.MIN_TIMESTEP) {
            return false;
        }
        if (timestampDeltaS > Util.MAX_TIMESTEP) {
            return false;
        }
        return true;
    };

    Util.getScreenWidth = function() {
        return Math.max(window.screen.width, window.screen.height) *
            window.devicePixelRatio;
    };

    Util.getScreenHeight = function() {
        return Math.min(window.screen.width, window.screen.height) *
            window.devicePixelRatio;
    };

    Util.requestFullscreen = function(element) {
        if (element.requestFullscreen) {
            element.requestFullscreen();
        } else if (element.webkitRequestFullscreen) {
            element.webkitRequestFullscreen();
        } else if (element.mozRequestFullScreen) {
            element.mozRequestFullScreen();
        } else if (element.msRequestFullscreen) {
            element.msRequestFullscreen();
        } else {
            return false;
        }

        return true;
    };

    Util.exitFullscreen = function() {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen();
        } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
        } else if (document.msExitFullscreen) {
            document.msExitFullscreen();
        } else {
            return false;
        }

        return true;
    };

    Util.getFullscreenElement = function() {
        return document.fullscreenElement ||
            document.webkitFullscreenElement ||
            document.mozFullScreenElement ||
            document.msFullscreenElement;
    };

    Util.linkProgram = function(gl, vertexSource, fragmentSource, attribLocationMap) {
        // No error checking for brevity.
        var vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vertexSource);
        gl.compileShader(vertexShader);

        var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, fragmentSource);
        gl.compileShader(fragmentShader);

        var program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);

        for (var attribName in attribLocationMap)
            gl.bindAttribLocation(program, attribLocationMap[attribName], attribName);

        gl.linkProgram(program);

        gl.deleteShader(vertexShader);
        gl.deleteShader(fragmentShader);

        return program;
    };

    Util.getProgramUniforms = function(gl, program) {
        var uniforms = {};
        var uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
        var uniformName = '';
        for (var i = 0; i < uniformCount; i++) {
            var uniformInfo = gl.getActiveUniform(program, i);
            uniformName = uniformInfo.name.replace('[0]', '');
            uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
        }
        return uniforms;
    };

    Util.orthoMatrix = function (out, left, right, bottom, top, near, far) {
        var lr = 1 / (left - right),
            bt = 1 / (bottom - top),
            nf = 1 / (near - far);
        out[0] = -2 * lr;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = -2 * bt;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = 2 * nf;
        out[11] = 0;
        out[12] = (left + right) * lr;
        out[13] = (top + bottom) * bt;
        out[14] = (far + near) * nf;
        out[15] = 1;
        return out;
    };

    Util.isMobile = function() {
        var check = false;
        (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
        return check;
    };


    module.exports = Util;

},{}],23:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var Emitter = _dereq_('./emitter.js');
    var Util = _dereq_('./util.js');
    var DeviceInfo = _dereq_('./device-info.js');

    var DEFAULT_VIEWER = 'CardboardV1';
    var VIEWER_KEY = 'WEBVR_CARDBOARD_VIEWER';
    var CLASS_NAME = 'webvr-polyfill-viewer-selector';

    /**
     * Creates a viewer selector with the options specified. Supports being shown
     * and hidden. Generates events when viewer parameters change. Also supports
     * saving the currently selected index in localStorage.
     */
    function ViewerSelector() {
        // Try to load the selected key from local storage. If none exists, use the
        // default key.
        try {
            this.selectedKey = localStorage.getItem(VIEWER_KEY) || DEFAULT_VIEWER;
        } catch (error) {
            console.error('Failed to load viewer profile: %s', error);
        }
        this.dialog = this.createDialog_(DeviceInfo.Viewers);
        this.root = null;
    }
    ViewerSelector.prototype = new Emitter();

    ViewerSelector.prototype.show = function(root) {
        this.root = root;

        root.appendChild(this.dialog);
        //console.log('ViewerSelector.show');

        // Ensure the currently selected item is checked.
        var selected = this.dialog.querySelector('#' + this.selectedKey);
        selected.checked = true;

        // Show the UI.
        this.dialog.style.display = 'block';
    };

    ViewerSelector.prototype.hide = function() {
        if (this.root && this.root.contains(this.dialog)) {
            this.root.removeChild(this.dialog);
        }
        //console.log('ViewerSelector.hide');
        this.dialog.style.display = 'none';
    };

    ViewerSelector.prototype.getCurrentViewer = function() {
        return DeviceInfo.Viewers[this.selectedKey];
    };

    ViewerSelector.prototype.getSelectedKey_ = function() {
        var input = this.dialog.querySelector('input[name=field]:checked');
        if (input) {
            return input.id;
        }
        return null;
    };

    ViewerSelector.prototype.onSave_ = function() {
        this.selectedKey = this.getSelectedKey_();
        if (!this.selectedKey || !DeviceInfo.Viewers[this.selectedKey]) {
            console.error('ViewerSelector.onSave_: this should never happen!');
            return;
        }

        this.emit('change', DeviceInfo.Viewers[this.selectedKey]);

        // Attempt to save the viewer profile, but fails in private mode.
        try {
            localStorage.setItem(VIEWER_KEY, this.selectedKey);
        } catch(error) {
            console.error('Failed to save viewer profile: %s', error);
        }
        this.hide();
    };

    /**
     * Creates the dialog.
     */
    ViewerSelector.prototype.createDialog_ = function(options) {
        var container = document.createElement('div');
        container.classList.add(CLASS_NAME);
        container.style.display = 'none';
        // Create an overlay that dims the background, and which goes away when you
        // tap it.
        var overlay = document.createElement('div');
        var s = overlay.style;
        s.position = 'fixed';
        s.left = 0;
        s.top = 0;
        s.width = '100%';
        s.height = '100%';
        s.background = 'rgba(0, 0, 0, 0.3)';
        overlay.addEventListener('click', this.hide.bind(this));

        var width = 280;
        var dialog = document.createElement('div');
        var s = dialog.style;
        s.boxSizing = 'border-box';
        s.position = 'fixed';
        s.top = '24px';
        s.left = '50%';
        s.marginLeft = (-width/2) + 'px';
        s.width = width + 'px';
        s.padding = '24px';
        s.overflow = 'hidden';
        s.background = '#fafafa';
        s.fontFamily = "'Roboto', sans-serif";
        s.boxShadow = '0px 5px 20px #666';

        dialog.appendChild(this.createH1_('Select your viewer'));
        for (var id in options) {
            dialog.appendChild(this.createChoice_(id, options[id].label));
        }
        dialog.appendChild(this.createButton_('Save', this.onSave_.bind(this)));

        container.appendChild(overlay);
        container.appendChild(dialog);

        return container;
    };

    ViewerSelector.prototype.createH1_ = function(name) {
        var h1 = document.createElement('h1');
        var s = h1.style;
        s.color = 'black';
        s.fontSize = '20px';
        s.fontWeight = 'bold';
        s.marginTop = 0;
        s.marginBottom = '24px';
        h1.innerHTML = name;
        return h1;
    };

    ViewerSelector.prototype.createChoice_ = function(id, name) {
        /*
         <div class="choice">
         <input id="v1" type="radio" name="field" value="v1">
         <label for="v1">Cardboard V1</label>
         </div>
         */
        var div = document.createElement('div');
        div.style.marginTop = '8px';
        div.style.color = 'black';

        var input = document.createElement('input');
        input.style.fontSize = '30px';
        input.setAttribute('id', id);
        input.setAttribute('type', 'radio');
        input.setAttribute('value', id);
        input.setAttribute('name', 'field');

        var label = document.createElement('label');
        label.style.marginLeft = '4px';
        label.setAttribute('for', id);
        label.innerHTML = name;

        div.appendChild(input);
        div.appendChild(label);

        return div;
    };

    ViewerSelector.prototype.createButton_ = function(label, onclick) {
        var button = document.createElement('button');
        button.innerHTML = label;
        var s = button.style;
        s.float = 'right';
        s.textTransform = 'uppercase';
        s.color = '#1094f7';
        s.fontSize = '14px';
        s.letterSpacing = 0;
        s.border = 0;
        s.background = 'none';
        s.marginTop = '16px';

        button.addEventListener('click', onclick);

        return button;
    };

    module.exports = ViewerSelector;

},{"./device-info.js":7,"./emitter.js":12,"./util.js":22}],24:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    var Util = _dereq_('./util.js');

    /**
     * Android and iOS compatible wakelock implementation.
     *
     * Refactored thanks to dkovalev@.
     */
    function AndroidWakeLock() {
        var video = document.createElement('video');

        video.addEventListener('ended', function() {
            video.play();
        });

        this.request = function() {
            if (video.paused) {
                // Base64 version of videos_src/no-sleep-120s.mp4.
                video.src = Util.base64('video/mp4', 'AAAAGGZ0eXBpc29tAAAAAG1wNDFhdmMxAAAIA21vb3YAAABsbXZoZAAAAADSa9v60mvb+gABX5AAlw/gAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAdkdHJhawAAAFx0a2hkAAAAAdJr2/rSa9v6AAAAAQAAAAAAlw/gAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAQAAAAHAAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAJcP4AAAAAAAAQAAAAAG3G1kaWEAAAAgbWRoZAAAAADSa9v60mvb+gAPQkAGjneAFccAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAABodtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAZHc3RibAAAAJdzdHNkAAAAAAAAAAEAAACHYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAMABwASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADFhdmNDAWQAC//hABlnZAALrNlfllw4QAAAAwBAAAADAKPFCmWAAQAFaOvssiwAAAAYc3R0cwAAAAAAAAABAAAAbgAPQkAAAAAUc3RzcwAAAAAAAAABAAAAAQAAA4BjdHRzAAAAAAAAAG4AAAABAD0JAAAAAAEAehIAAAAAAQA9CQAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEATEtAAAAAAQAehIAAAAABAAAAAAAAAAEAD0JAAAAAAQBMS0AAAAABAB6EgAAAAAEAAAAAAAAAAQAPQkAAAAABAExLQAAAAAEAHoSAAAAAAQAAAAAAAAABAA9CQAAAAAEALcbAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAABuAAAAAQAAAcxzdHN6AAAAAAAAAAAAAABuAAADCQAAABgAAAAOAAAADgAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABIAAAAOAAAADAAAAAwAAAASAAAADgAAAAwAAAAMAAAAEgAAAA4AAAAMAAAADAAAABMAAAAUc3RjbwAAAAAAAAABAAAIKwAAACt1ZHRhAAAAI6llbmMAFwAAdmxjIDIuMi4xIHN0cmVhbSBvdXRwdXQAAAAId2lkZQAACRRtZGF0AAACrgX//6vcRem95tlIt5Ys2CDZI+7veDI2NCAtIGNvcmUgMTQyIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDEzIG1lPWhleCBzdWJtZT03IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4ZWRfcmVmPTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0xIDh4OGRjdD0xIGNxbT0wIGRlYWR6b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJvbWFfcXBfb2Zmc2V0PS0yIHRocmVhZHM9MTIgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1hYnIgbWJ0cmVlPTEgYml0cmF0ZT0xMDAgcmF0ZXRvbD0xLjAgcWNvbXA9MC42MCBxcG1pbj0xMCBxcG1heD01MSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAU2WIhAAQ/8ltlOe+cTZuGkKg+aRtuivcDZ0pBsfsEi9p/i1yU9DxS2lq4dXTinViF1URBKXgnzKBd/Uh1bkhHtMrwrRcOJslD01UB+fyaL6ef+DBAAAAFEGaJGxBD5B+v+a+4QqF3MgBXz9MAAAACkGeQniH/+94r6EAAAAKAZ5hdEN/8QytwAAAAAgBnmNqQ3/EgQAAAA5BmmhJqEFomUwIIf/+4QAAAApBnoZFESw//76BAAAACAGepXRDf8SBAAAACAGep2pDf8SAAAAADkGarEmoQWyZTAgh//7gAAAACkGeykUVLD//voEAAAAIAZ7pdEN/xIAAAAAIAZ7rakN/xIAAAAAOQZrwSahBbJlMCCH//uEAAAAKQZ8ORRUsP/++gQAAAAgBny10Q3/EgQAAAAgBny9qQ3/EgAAAAA5BmzRJqEFsmUwIIf/+4AAAAApBn1JFFSw//76BAAAACAGfcXRDf8SAAAAACAGfc2pDf8SAAAAADkGbeEmoQWyZTAgh//7hAAAACkGflkUVLD//voAAAAAIAZ+1dEN/xIEAAAAIAZ+3akN/xIEAAAAOQZu8SahBbJlMCCH//uAAAAAKQZ/aRRUsP/++gQAAAAgBn/l0Q3/EgAAAAAgBn/tqQ3/EgQAAAA5Bm+BJqEFsmUwIIf/+4QAAAApBnh5FFSw//76AAAAACAGePXRDf8SAAAAACAGeP2pDf8SBAAAADkGaJEmoQWyZTAgh//7gAAAACkGeQkUVLD//voEAAAAIAZ5hdEN/xIAAAAAIAZ5jakN/xIEAAAAOQZpoSahBbJlMCCH//uEAAAAKQZ6GRRUsP/++gQAAAAgBnqV0Q3/EgQAAAAgBnqdqQ3/EgAAAAA5BmqxJqEFsmUwIIf/+4AAAAApBnspFFSw//76BAAAACAGe6XRDf8SAAAAACAGe62pDf8SAAAAADkGa8EmoQWyZTAgh//7hAAAACkGfDkUVLD//voEAAAAIAZ8tdEN/xIEAAAAIAZ8vakN/xIAAAAAOQZs0SahBbJlMCCH//uAAAAAKQZ9SRRUsP/++gQAAAAgBn3F0Q3/EgAAAAAgBn3NqQ3/EgAAAAA5Bm3hJqEFsmUwIIf/+4QAAAApBn5ZFFSw//76AAAAACAGftXRDf8SBAAAACAGft2pDf8SBAAAADkGbvEmoQWyZTAgh//7gAAAACkGf2kUVLD//voEAAAAIAZ/5dEN/xIAAAAAIAZ/7akN/xIEAAAAOQZvgSahBbJlMCCH//uEAAAAKQZ4eRRUsP/++gAAAAAgBnj10Q3/EgAAAAAgBnj9qQ3/EgQAAAA5BmiRJqEFsmUwIIf/+4AAAAApBnkJFFSw//76BAAAACAGeYXRDf8SAAAAACAGeY2pDf8SBAAAADkGaaEmoQWyZTAgh//7hAAAACkGehkUVLD//voEAAAAIAZ6ldEN/xIEAAAAIAZ6nakN/xIAAAAAOQZqsSahBbJlMCCH//uAAAAAKQZ7KRRUsP/++gQAAAAgBnul0Q3/EgAAAAAgBnutqQ3/EgAAAAA5BmvBJqEFsmUwIIf/+4QAAAApBnw5FFSw//76BAAAACAGfLXRDf8SBAAAACAGfL2pDf8SAAAAADkGbNEmoQWyZTAgh//7gAAAACkGfUkUVLD//voEAAAAIAZ9xdEN/xIAAAAAIAZ9zakN/xIAAAAAOQZt4SahBbJlMCCH//uEAAAAKQZ+WRRUsP/++gAAAAAgBn7V0Q3/EgQAAAAgBn7dqQ3/EgQAAAA5Bm7xJqEFsmUwIIf/+4AAAAApBn9pFFSw//76BAAAACAGf+XRDf8SAAAAACAGf+2pDf8SBAAAADkGb4EmoQWyZTAgh//7hAAAACkGeHkUVLD//voAAAAAIAZ49dEN/xIAAAAAIAZ4/akN/xIEAAAAOQZokSahBbJlMCCH//uAAAAAKQZ5CRRUsP/++gQAAAAgBnmF0Q3/EgAAAAAgBnmNqQ3/EgQAAAA5BmmhJqEFsmUwIIf/+4QAAAApBnoZFFSw//76BAAAACAGepXRDf8SBAAAACAGep2pDf8SAAAAADkGarEmoQWyZTAgh//7gAAAACkGeykUVLD//voEAAAAIAZ7pdEN/xIAAAAAIAZ7rakN/xIAAAAAPQZruSahBbJlMFEw3//7B');
                video.play();
            }
        };

        this.release = function() {
            video.pause();
            video.src = '';
        };
    }

    function iOSWakeLock() {
        var timer = null;

        this.request = function() {
            if (!timer) {
                timer = setInterval(function() {
                    window.location = window.location;
                    setTimeout(window.stop, 0);
                }, 30000);
            }
        }

        this.release = function() {
            if (timer) {
                clearInterval(timer);
                timer = null;
            }
        }
    }


    function getWakeLock() {
        var userAgent = navigator.userAgent || navigator.vendor || window.opera;
        if (userAgent.match(/iPhone/i) || userAgent.match(/iPod/i)) {
            return iOSWakeLock;
        } else {
            return AndroidWakeLock;
        }
    }

    module.exports = getWakeLock();
},{"./util.js":22}],25:[function(_dereq_,module,exports){
    /*
     * Copyright 2015 Google Inc. All Rights Reserved.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

// Polyfill ES6 Promises (mostly for IE 11).
    _dereq_('es6-promise').polyfill();

    var CardboardVRDisplay = _dereq_('./cardboard-vr-display.js');
    var MouseKeyboardVRDisplay = _dereq_('./mouse-keyboard-vr-display.js');
// Uncomment to add positional tracking via webcam.
//var WebcamPositionSensorVRDevice = require('./webcam-position-sensor-vr-device.js');
    var VRDisplay = _dereq_('./base.js').VRDisplay;
    var HMDVRDevice = _dereq_('./base.js').HMDVRDevice;
    var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice;
    var VRDisplayHMDDevice = _dereq_('./display-wrappers.js').VRDisplayHMDDevice;
    var VRDisplayPositionSensorDevice = _dereq_('./display-wrappers.js').VRDisplayPositionSensorDevice;

    function WebVRPolyfill() {
        this.displays = [];
        this.devices = []; // For deprecated objects
        this.devicesPopulated = false;
        this.nativeWebVRAvailable = this.isWebVRAvailable();
        this.nativeLegacyWebVRAvailable = this.isDeprecatedWebVRAvailable();

        if (!this.nativeLegacyWebVRAvailable) {
            if (!this.nativeWebVRAvailable) {
                this.enablePolyfill();
            }
            if (WebVRConfig.ENABLE_DEPRECATED_API) {
                this.enableDeprecatedPolyfill();
            }
        }
    }

    WebVRPolyfill.prototype.isWebVRAvailable = function() {
        return ('getVRDisplays' in navigator);
    };

    WebVRPolyfill.prototype.isDeprecatedWebVRAvailable = function() {
        return ('getVRDevices' in navigator) || ('mozGetVRDevices' in navigator);
    };

    WebVRPolyfill.prototype.populateDevices = function() {
        if (this.devicesPopulated) {
            return;
        }

        // Initialize our virtual VR devices.
        var vrDisplay = null;

        // Add a Cardboard VRDisplay on compatible mobile devices
        if (this.isCardboardCompatible()) {
            vrDisplay = new CardboardVRDisplay();
            this.displays.push(vrDisplay);

            // For backwards compatibility
            if (WebVRConfig.ENABLE_DEPRECATED_API) {
                this.devices.push(new VRDisplayHMDDevice(vrDisplay));
                this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
            }
        }

        // Add a Mouse and Keyboard driven VRDisplay for desktops/laptops
        if (!this.isMobile() && !WebVRConfig.MOUSE_KEYBOARD_CONTROLS_DISABLED) {
            vrDisplay = new MouseKeyboardVRDisplay();
            this.displays.push(vrDisplay);

            // For backwards compatibility
            if (WebVRConfig.ENABLE_DEPRECATED_API) {
                this.devices.push(new VRDisplayHMDDevice(vrDisplay));
                this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
            }
        }

        // Uncomment to add positional tracking via webcam.
        //if (!this.isMobile() && WebVRConfig.ENABLE_DEPRECATED_API) {
        //  positionDevice = new WebcamPositionSensorVRDevice();
        //  this.devices.push(positionDevice);
        //}

        this.devicesPopulated = true;
    };

    WebVRPolyfill.prototype.enablePolyfill = function() {
        // Provide navigator.getVRDisplays.
        navigator.getVRDisplays = this.getVRDisplays.bind(this);

        // Provide the VRDisplay object.
        window.VRDisplay = VRDisplay;
    };

    WebVRPolyfill.prototype.enableDeprecatedPolyfill = function() {
        // Provide navigator.getVRDevices.
        navigator.getVRDevices = this.getVRDevices.bind(this);

        // Provide the CardboardHMDVRDevice and PositionSensorVRDevice objects.
        window.HMDVRDevice = HMDVRDevice;
        window.PositionSensorVRDevice = PositionSensorVRDevice;
    };

    WebVRPolyfill.prototype.getVRDisplays = function() {
        this.populateDevices();
        var displays = this.displays;
        return new Promise(function(resolve, reject) {
            try {
                resolve(displays);
            } catch (e) {
                reject(e);
            }
        });
    };

    WebVRPolyfill.prototype.getVRDevices = function() {
        console.warn('getVRDevices is deprecated. Please update your code to use getVRDisplays instead.');
        var self = this;
        return new Promise(function(resolve, reject) {
            try {
                if (!self.devicesPopulated) {
                    if (self.nativeWebVRAvailable) {
                        return navigator.getVRDisplays(function(displays) {
                            for (var i = 0; i < displays.length; ++i) {
                                self.devices.push(new VRDisplayHMDDevice(displays[i]));
                                self.devices.push(new VRDisplayPositionSensorDevice(displays[i]));
                            }
                            self.devicesPopulated = true;
                            resolve(self.devices);
                        }, reject);
                    }

                    if (self.nativeLegacyWebVRAvailable) {
                        return (navigator.getVRDDevices || navigator.mozGetVRDevices)(function(devices) {
                            for (var i = 0; i < devices.length; ++i) {
                                if (devices[i] instanceof HMDVRDevice) {
                                    self.devices.push(displays[i]);
                                }
                                if (devices[i] instanceof PositionSensorVRDevice) {
                                    self.devices.push(devices[i]);
                                }
                            }
                            self.devicesPopulated = true;
                            resolve(self.devices);
                        }, reject);
                    }
                }

                self.populateDevices();
                resolve(self.devices);
            } catch (e) {
                reject(e);
            }
        });
    };

    /**
     * Determine if a device is mobile.
     */
    WebVRPolyfill.prototype.isMobile = function() {
        return /Android/i.test(navigator.userAgent) ||
            /iPhone|iPad|iPod/i.test(navigator.userAgent);
    };

    WebVRPolyfill.prototype.isCardboardCompatible = function() {
        // For now, support all iOS and Android devices.
        // Also enable the WebVRConfig.FORCE_VR flag for debugging.
        return this.isMobile() || WebVRConfig.FORCE_ENABLE_VR;
    };

    module.exports = WebVRPolyfill;

},{"./base.js":2,"./cardboard-vr-display.js":5,"./display-wrappers.js":8,"./mouse-keyboard-vr-display.js":14,"es6-promise":1}],26:[function(_dereq_,module,exports){
// shim for using process in browser

    var process = module.exports = {};
    var queue = [];
    var draining = false;
    var currentQueue;
    var queueIndex = -1;

    function cleanUpNextTick() {
        draining = false;
        if (currentQueue.length) {
            queue = currentQueue.concat(queue);
        } else {
            queueIndex = -1;
        }
        if (queue.length) {
            drainQueue();
        }
    }

    function drainQueue() {
        if (draining) {
            return;
        }
        var timeout = setTimeout(cleanUpNextTick);
        draining = true;

        var len = queue.length;
        while(len) {
            currentQueue = queue;
            queue = [];
            while (++queueIndex < len) {
                if (currentQueue) {
                    currentQueue[queueIndex].run();
                }
            }
            queueIndex = -1;
            len = queue.length;
        }
        currentQueue = null;
        draining = false;
        clearTimeout(timeout);
    }

    process.nextTick = function (fun) {
        var args = new Array(arguments.length - 1);
        if (arguments.length > 1) {
            for (var i = 1; i < arguments.length; i++) {
                args[i - 1] = arguments[i];
            }
        }
        queue.push(new Item(fun, args));
        if (queue.length === 1 && !draining) {
            setTimeout(drainQueue, 0);
        }
    };

// v8 likes predictible objects
    function Item(fun, array) {
        this.fun = fun;
        this.array = array;
    }
    Item.prototype.run = function () {
        this.fun.apply(null, this.array);
    };
    process.title = 'browser';
    process.browser = true;
    process.env = {};
    process.argv = [];
    process.version = ''; // empty string to avoid regexp issues
    process.versions = {};

    function noop() {}

    process.on = noop;
    process.addListener = noop;
    process.once = noop;
    process.off = noop;
    process.removeListener = noop;
    process.removeAllListeners = noop;
    process.emit = noop;

    process.binding = function (name) {
        throw new Error('process.binding is not supported');
    };

    process.cwd = function () { return '/' };
    process.chdir = function (dir) {
        throw new Error('process.chdir is not supported');
    };
    process.umask = function() { return 0; };

},{}]},{},[13]);